Skip to content

Instantly share code, notes, and snippets.

@robdodson
Created January 10, 2020 16:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robdodson/b78084c1ed08d7e0cfc2bba837e884bf to your computer and use it in GitHub Desktop.
Save robdodson/b78084c1ed08d7e0cfc2bba837e884bf to your computer and use it in GitHub Desktop.
This file has been truncated, but you can view the full file.
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>web.dev</title>
<subtitle>Let&#39;s build the future of the web.</subtitle>
<link href="https://web.dev/feed.xml" rel="self"/>
<link href="https://web.dev"/>
<updated>2018-11-04T16:00:00-08:00</updated>
<id>https://web.dev/</id>
<author>
<name>Google Developers</name>
</author>
<entry>
<title>Adaptive icon support in PWAs with maskable icons</title>
<link href="https://web.dev/maskable-icon/"/>
<updated>2019-12-18T16:00:00-08:00</updated>
<id>https://web.dev/maskable-icon/</id>
<content type="html">&lt;h2 id=&quot;what&quot;&gt;What are maskable icons? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;ve installed a Progressive Web App on a recent Android phone, you might notice the icon shows up with a white background. Android Oreo introduced adaptive icons, which display app icons in a variety of shapes across different device models. Icons that don&#39;t follow this new format are given white backgrounds.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/maskable-icon/homescreen-any.png&quot; alt=&quot;PWA icons in white circles on Android&quot; style=&quot;width: 400px; max-width: 100%&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Transparent PWA icons appear inside white circles on Android
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Maskable icons are a new icon format that give you more control and let your Progressive Web App use adaptive icons. If you supply a maskable icon, your icon can fill up the entire shape and look great on all Android devices. Firefox and Chrome have recently added support for this new format, and you can adopt it in your apps.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/maskable-icon/homescreen-maskable.png&quot; alt=&quot;PWA icons covering the entire circle on Android&quot; style=&quot;width: 400px; max-width: 100%&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Maskable icons cover the entire circle instead
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;are-my-current-icons-ready&quot;&gt;Are my current icons ready? &lt;a class=&quot;w-headline-link&quot; href=&quot;#are-my-current-icons-ready&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since maskable icons need to support a variety of shapes, you supply an opaque image with some padding that the browser can later crop into the desired shape and size. It&#39;s best not to rely on any particular shape, since it can vary by browser and per platform.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;video class=&quot;w-screenshot&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; aria-label=&quot;Different masks applied to a maskable icon&quot;&gt;
&lt;source src=&quot;https://web.dev/maskable-icon/fugu-mask.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://web.dev/maskable-icon/fugu-mask.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Different platform specific shapes
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Luckily, there&#39;s a well-defined and &lt;a href=&quot;https://w3c.github.io/manifest/#icon-masks&quot;&gt;standardized&lt;/a&gt; &amp;quot;minimum safe zone&amp;quot; that all platforms respect. The important parts of your icon, such as your logo, should be within a circular area in the center of the icon with a radius equal to 40% of the icon width. The outer 10% edge may be cropped.&lt;/p&gt;
&lt;p&gt;You can check which parts of your icons land within the safe zone with Chrome DevTools. With your Progressive Web App open, launch DevTools and navigate to the &lt;strong&gt;Application&lt;/strong&gt; panel. In the &lt;strong&gt;Icons&lt;/strong&gt; section, you can choose to &lt;strong&gt;Show only the minimum safe area for maskable icons&lt;/strong&gt;. Your icons will be trimmed so that only the safe area is visible. If your logo is visible within this safe area, you&#39;re good to go.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/maskable-icon/devtools.png&quot; class=&quot;w-screenshot&quot; alt=&quot;Applications panel in DevTools displaying PWA icons with edges cropped&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The Applications panel
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you want to preview your maskable icon in other shapes it may appear in on Android, I&#39;ve created a tool called &lt;a href=&quot;https://maskable.app/&quot;&gt;Maskable.app&lt;/a&gt;. Open an icon, then Maskable.app will let you try out various shapes and sizes, and you can share the preview with others on your team.&lt;/p&gt;
&lt;h2 id=&quot;how-do-i-adopt-maskable-icons&quot;&gt;How do I adopt maskable icons? &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-do-i-adopt-maskable-icons&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you want to create a maskable icon based off your existing icon, you can use the &lt;a href=&quot;https://maskable.app/editor&quot;&gt;Maskable.app Editor&lt;/a&gt;. Upload your icon, adjust the color and size, then export the image.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/maskable-icon/maskable-app-editor.png&quot; class=&quot;w-screenshot&quot; alt=&quot;Maskable.app Editor screenshot&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Creating icons in Maskable.app Editor
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once you&#39;ve created a maskable icon image and tested it out in DevTools, you&#39;ll need to update your &lt;a href=&quot;https://developers.google.com/web/fundamentals/web-app-manifest&quot;&gt;Web App Manifest&lt;/a&gt; to point to the new assets. The Web App Manifest provides information about your web app in a JSON file, and includes an &lt;a href=&quot;https://developers.google.com/web/fundamentals/web-app-manifest#icons&quot;&gt;&lt;code&gt;icons&lt;/code&gt; array&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the inclusion of maskable icons, a new property value has been added for image resources listed in a Web App Manifest. The &lt;code&gt;purpose&lt;/code&gt; field tells the browser how your icon should be used. By default, icons will have a purpose of &lt;code&gt;&amp;quot;any&amp;quot;&lt;/code&gt;. These icons will be resized on top of a white background on Android.&lt;/p&gt;
&lt;p&gt;Maskable icons should use a different purpose: &lt;code&gt;&amp;quot;maskable&amp;quot;&lt;/code&gt;. This indicates that an image is meant to be used with icon masks, giving you more control over the result. This way, your icons will not have a white background. You can also specify multiple space-separated purposes (for example, &lt;code&gt;&amp;quot;any maskable&amp;quot;&lt;/code&gt;), if you want your maskable icon to be used without a mask on other devices.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; …&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;icons&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; …&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;path/to/maskable_icon.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;196x196&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;purpose&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;any maskable&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;-- New property value `&quot;maskable&quot;`&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; …&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;With this, you can go forth and create your own maskable icons, making sure you app looks great edge-to-edge (and for what it&#39;s worth, circle to circle, oval to oval 😄).&lt;/p&gt;
</content>
<author>
<name>Tiger Oakes</name>
</author>
</entry>
<entry>
<title>Improving page dismissal in synchronous XMLHttpRequest()</title>
<link href="https://web.dev/disallow-synchronous-xhr/"/>
<updated>2019-12-17T16:00:00-08:00</updated>
<id>https://web.dev/disallow-synchronous-xhr/</id>
<content type="html">&lt;p&gt;It&#39;s common for a page or app to have unsubmitted analytics or other data at the
time a user closes it. To prevent data loss, some sites use a synchronous call
to &lt;code&gt;XMLHttpRequest()&lt;/code&gt; to keep the page or app open until its data is passed to
the server. Not only are there better ways to save data, but this technique creates
a bad user experience by delaying closing of the page for up to several seconds.&lt;/p&gt;
&lt;p&gt;This practice needs to change, and browsers are responding. The &lt;code&gt;XMLHttpRequest()&lt;/code&gt;
specification is already &lt;a href=&quot;https://xhr.spec.whatwg.org/#sync-warning&quot;&gt;slated for deprecation and
removal&lt;/a&gt;. Chrome 80 takes the first
step by disallowing synchronous calls inside several event handlers,
specifically &lt;code&gt;beforeunload&lt;/code&gt;, &lt;code&gt;unload&lt;/code&gt;, &lt;code&gt;pagehide&lt;/code&gt;, and &lt;code&gt;visibilitychange&lt;/code&gt; when
they are fired in the dismissal. WebKit also recently landed &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=204912&quot;&gt;a commit implementing
the same behavior change&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this article I&#39;ll briefly describe options for those who need time to update
their sites and outline the alternatives to &lt;code&gt;XMLHttpRequest()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;temporary-opt-outs&quot;&gt;Temporary opt-outs &lt;a class=&quot;w-headline-link&quot; href=&quot;#temporary-opt-outs&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chrome does not simply want to pull the plug on &lt;code&gt;XMLHttpRequest()&lt;/code&gt;, which is why a few
temporary opt-out options are available. For sites on the internet, &lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/4391009636686233601&quot;&gt;an origin
trial is
available&lt;/a&gt;.
With this, you add an origin-specific token to your page headers that enables
synchronous &lt;code&gt;XMLHttpRequest()&lt;/code&gt; calls. This option ends shortly before Chrome 86
ships, sometime in late October of 2020. Enterprise Chrome customers can also
use the &lt;code&gt;AllowSyncXHRInPageDismissal&lt;/code&gt; policy flag, which ends at the same time.&lt;/p&gt;
&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives &lt;a class=&quot;w-headline-link&quot; href=&quot;#alternatives&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Regardless of how you send data back to the server, it&#39;s best to avoid waiting
until page unload to send all the data at once. Aside from creating a bad user
experience, you risk data loss if something goes wrong. Unload events &lt;a href=&quot;https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/&quot;&gt;often
don&#39;t fire on mobile
browsers&lt;/a&gt;
because there are &lt;a href=&quot;https://developers.google.com/web/updates/2018/07/page-lifecycle-api&quot;&gt;many ways to
close&lt;/a&gt; a
tab or browser on mobile operating systems without the &lt;code&gt;unload&lt;/code&gt; event firing. With
&lt;code&gt;XMLHttpRequest()&lt;/code&gt;, using small payloads was a choice. Now it&#39;s a requirement. Both of
its alternatives have an upload limit of 64 KB per context, as required
by the specification.&lt;/p&gt;
&lt;h3 id=&quot;fetch-keepalive&quot;&gt;Fetch keepalive &lt;a class=&quot;w-headline-link&quot; href=&quot;#fetch-keepalive&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt;
provides a robust means of dealing with server interactions and &lt;a href=&quot;https://fetch.spec.whatwg.org/#preface&quot;&gt;a consistent
interface&lt;/a&gt; for use across different
platform APIs. Among its options is &lt;code&gt;keepalive&lt;/code&gt;, which ensures that a request
continues whether or not the page that made it stays open:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;unload&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/siteAnalytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; method&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; body&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getStatistics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; keepalive&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The &lt;code&gt;fetch()&lt;/code&gt; method has the advantage of greater control over what&#39;s sent to
the server. What I don&#39;t show in the example is that &lt;code&gt;fetch()&lt;/code&gt; also returns a
promise that resolves with a &lt;code&gt;Response&lt;/code&gt; object. Since I&#39;m trying to get out of the
way of the page&#39;s unloading, I chose not to do anything with it.&lt;/p&gt;
&lt;h3 id=&quot;sendbeacon()&quot;&gt;SendBeacon() &lt;a class=&quot;w-headline-link&quot; href=&quot;#sendbeacon()&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon&quot;&gt;&lt;code&gt;SendBeacon()&lt;/code&gt;&lt;/a&gt;
actually uses the Fetch API under the hood, which is why it has the same
64 KB payload limitation and why it also ensures that a request continues
after a page unload. Its primary advantage is its simplicity. It lets you
submit your data with a single line of code:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;unload&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendBeacon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/siteAnalytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getStatistics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Browser_compatibility&quot;&gt;increased availability of
&lt;code&gt;fetch()&lt;/code&gt;&lt;/a&gt;
across browsers, &lt;code&gt;XMLHttpRequest()&lt;/code&gt; will hopefully be removed
from the web platform at some point. Browser vendors agree it should be removed, but it will
take time. Deprecating one of its worst use cases is a first step that improves
the user experience for everyone.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@thatsmrbio?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Matthew Hamilton&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/roadblock?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
<author>
<name>Joe Medley</name>
</author>
</entry>
<entry>
<title>Threading the web with module workers</title>
<link href="https://web.dev/module-workers/"/>
<updated>2019-12-16T16:00:00-08:00</updated>
<id>https://web.dev/module-workers/</id>
<content type="html">&lt;style&gt;
.wm-filename {
margin-bottom: 0;
opacity: 0.7;
}
&lt;/style&gt;
&lt;p&gt;JavaScript is single-threaded, which means it can only perform one operation at a time. This is
intuitive and works well for lots of cases on the web, but can become problematic when we need to
do heavy lifting tasks like data processing, parsing, computation, or analysis. As more and more
complex applications are delivered on the web, there&#39;s an increased need for multi-threaded
processing.&lt;/p&gt;
&lt;p&gt;On the web platform, the main primitive for threading and parallelism is the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers&quot;&gt;Web
Workers API&lt;/a&gt;.
Workers are a lightweight abstraction on top of &lt;a href=&quot;https://en.wikipedia.org/wiki/Thread_%28computing%29&quot;&gt;operating system
threads&lt;/a&gt; that expose a message passing API
for inter-thread communication. This can be immensely useful when performing costly computations or
operating on large datasets, allowing the main thread to run smoothly while performing the
expensive operations on one or more background threads.&lt;/p&gt;
&lt;p&gt;Here&#39;s a typical example of worker usage, where a worker script listens for messages from the main
thread and responds by sending back messages of its own:&lt;/p&gt;
&lt;p class=&quot;wm-filename&quot;&gt;page.js:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;worker.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p class=&quot;wm-filename&quot;&gt;worker.js:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;world&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The Web Worker API has been available in most browsers for over ten years. While that
means workers have excellent browser support and are well-optimized, it also means they long
predate JavaScript modules. Since there was no module system when workers were designed, the API
for loading code into a worker and composing scripts has remained similar to the synchronous script
loading approaches common in 2009.&lt;/p&gt;
&lt;h2 id=&quot;history:-classic-workers&quot;&gt;History: classic workers &lt;a class=&quot;w-headline-link&quot; href=&quot;#history:-classic-workers&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Worker constructor takes a &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#classic-script&quot;&gt;classic
script&lt;/a&gt; URL, which is
relative to the document URL. It immediately returns a reference to the new worker instance,
which exposes a messaging interface as well as a &lt;code&gt;terminate()&lt;/code&gt; method that immediately stops and
destroys the worker.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;worker.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;An &lt;code&gt;importScripts()&lt;/code&gt; function is available within web workers for loading additional code, but it
pauses execution of the worker in order to fetch and evaluate each script. It also executes scripts
in the global scope like a classic &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, meaning the variables in one script can be
overwritten by the variables in another.&lt;/p&gt;
&lt;p class=&quot;wm-filename&quot;&gt;worker.js:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;importScripts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;greet.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ^ could block for seconds&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p class=&quot;wm-filename&quot;&gt;greet.js:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// global to the whole worker&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;world&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;For this reason, web workers have historically imposed an outsized effect on the architecture of an
application. Developers have had to create clever tooling and workarounds to make it possible to
use web workers without giving up modern development practises. As an example, bundlers like
webpack embed a small module loader implementation into generated code that uses &lt;code&gt;importScripts()&lt;/code&gt;
for code loading, but wraps modules in functions to avoid variable collisions and simulate
dependency imports and exports.&lt;/p&gt;
&lt;h2 id=&quot;enter-module-workers&quot;&gt;Enter module workers &lt;a class=&quot;w-headline-link&quot; href=&quot;#enter-module-workers&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A new mode for web workers with the ergonomics and performance benefits of &lt;a href=&quot;https://v8.dev/features/modules&quot;&gt;JavaScript
modules&lt;/a&gt; is shipping in Chrome 80, called module workers. The
&lt;code&gt;Worker&lt;/code&gt; constructor now accepts a new &lt;code&gt;{type:&amp;quot;module&amp;quot;}&lt;/code&gt; option, which changes script loading and
execution to match &lt;code&gt;&amp;lt;script type=&amp;quot;module&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;worker.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;module&#39;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Since module workers are standard JavaScript modules, they can use import and export statements. As
with all JavaScript modules, dependencies are only executed once in a given context (main thread,
worker, etc.), and all future imports reference the already-executed module instance. The loading
and execution of JavaScript modules is also optimized by browsers. A module&#39;s dependencies can be
loaded prior to the module being executed, which allows entire module trees to be loaded in
parallel. Module loading also caches parsed code, which means modules that are used on the main
thread and in a worker only need to be parsed once.&lt;/p&gt;
&lt;p&gt;Moving to JavaScript modules also enables the use of &lt;a href=&quot;https://v8.dev/features/dynamic-import&quot;&gt;dynamic
import&lt;/a&gt; for lazy-loading code without blocking execution of
the worker. Dynamic import is much more explicit than using &lt;code&gt;importScripts()&lt;/code&gt; to load dependencies,
since the imported module&#39;s exports are returned rather than relying on global variables.&lt;/p&gt;
&lt;p class=&quot;wm-filename&quot;&gt;worker.js:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sayHello &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./greet.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p class=&quot;wm-filename&quot;&gt;greet.js:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; greetings &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./data.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; greetings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hello&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;To ensure great performance, the old &lt;code&gt;importScripts()&lt;/code&gt; method is not available within module
workers. Switching workers to use JavaScript modules means all code is loaded in &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode&quot;&gt;strict
mode&lt;/a&gt;. Another
notable change is that the value of &lt;code&gt;this&lt;/code&gt; in the top-level scope of a JavaScript module is
&lt;code&gt;undefined&lt;/code&gt;, whereas in classic workers the value is the worker&#39;s global scope. Fortunately, there
has always been a &lt;code&gt;self&lt;/code&gt; global that provides a reference to the global scope. It&#39;s available in
all types of workers including service workers, as well as in the DOM.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Module workers also remove support for HTML-style comments. Did you know you could use
HTML comments in web worker scripts?&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;preload-workers-with-modulepreload&quot;&gt;Preload workers with &lt;code&gt;modulepreload&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#preload-workers-with-modulepreload&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One substantial performance improvement that comes with module workers is the ability to preload
workers and their dependencies. With module workers, scripts are loaded and executed as standard
JavaScript modules, which means they can be preloaded and even pre-parsed using &lt;code&gt;modulepreload&lt;/code&gt;:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- preloads worker.js and its dependencies: --&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;modulepreload&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;worker.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// our worker code is likely already parsed and ready to execute!&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;worker.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;module&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Preloaded modules can also be used by both the main thread and module workers. This is useful for
modules that are imported in both contexts, or in cases where it&#39;s not possible to know in advance
whether a module will be used on the main thread or in a worker.&lt;/p&gt;
&lt;p&gt;Previously, the options available for preloading web worker scripts were limited and not
necessarily reliable. Classic workers had their own &amp;quot;worker&amp;quot; resource type for preloading, but no
browsers implemented &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot; as=&amp;quot;worker&amp;quot;&amp;gt;&lt;/code&gt;. As a result, the primary technique
available for preloading web workers was to use &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt;, which relied entirely
on the HTTP cache. When used in combination with the correct caching headers, this made it possible
to avoid worker instantiation having to wait to download the worker script. However, unlike
&lt;code&gt;modulepreload&lt;/code&gt; this technique did not support preloading dependencies or pre-parsing.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 480px; width: 100%;&quot;&gt;
&lt;iframe src=&quot;https://glitch.com/embed/#!/embed/worker-preloading?previewSize=100&amp;attributionHidden=true&quot; alt=&quot;worker-preloading on Glitch&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;what-about-service-worker&quot;&gt;What about service worker? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-about-service-worker&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The service worker specification &lt;a href=&quot;https://w3c.github.io/ServiceWorker/#service-worker-concept&quot;&gt;has already been
updated&lt;/a&gt; to support accepting a
JavaScript module as the entry point, using the same &lt;code&gt;{type:&amp;quot;module&amp;quot;}&lt;/code&gt; option as module workers,
however this change has yet to be implemented in browsers. Once that happens, it will be possible
to instantiate a service worker using a JavaScript module using the following code:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sw.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;module&#39;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Now that the specification has been updated, browsers are beginning to implement the new behavior.
This takes time because there are some extra complications associated with bringing JavaScript
modules to service worker. Service worker registration needs to &lt;a href=&quot;https://chromestatus.com/feature/6533131347689472&quot;&gt;compare imported scripts
with their previous cached versions&lt;/a&gt; when
determining whether to trigger an update, and this needs to be implemented for JavaScript modules
when used for service workers. Also, service workers need to be able to &lt;a href=&quot;https://chromestatus.com/feature/5897293530136576&quot;&gt;bypass the
cache&lt;/a&gt; for scripts in certain cases when
checking for updates.&lt;/p&gt;
&lt;h2 id=&quot;additional-resources-and-further-reading&quot;&gt;Additional resources and further reading &lt;a class=&quot;w-headline-link&quot; href=&quot;#additional-resources-and-further-reading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/5761300827209728&quot;&gt;Feature status, browser consensus and standardization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/whatwg/html/pull/608&quot;&gt;Original module workers spec addition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;JavaScript modules for service workers: &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=824647&quot;&gt;Chrome implementation status&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
<author>
<name>Jason Miller</name>
</author>
</entry>
<entry>
<title>Speed tooling evolutions: highlights from Chrome Developer Summit 2019</title>
<link href="https://web.dev/speed-tooling-evolutions-cds-2019/"/>
<updated>2019-12-15T16:00:00-08:00</updated>
<id>https://web.dev/speed-tooling-evolutions-cds-2019/</id>
<content type="html">&lt;p&gt;At Chrome Developer Summit, Paul Irish and I announced updates to
Lighthouse—&lt;a href=&quot;https://web.dev/lighthouse-evolution-cds-2019&quot;&gt;Lighthouse CI, new performance score formula, and
more&lt;/a&gt;. Along with big Lighthouse news, we
presented exciting performance tooling developments including new performance
metrics, updates to PageSpeed Insights and Chrome User Experience Report (CrUX),
and insights from the Web Almanac&#39;s analysis of the web ecosystem.&lt;/p&gt;
&lt;h2 id=&quot;new-performance-metrics&quot;&gt;New performance metrics &lt;a class=&quot;w-headline-link&quot; href=&quot;#new-performance-metrics&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Measuring the nuances of a user&#39;s experience is the key to quantifying the
impact it has on your bottom line and tracking improvements and regressions.
Over time, new metrics have evolved to capture those nuances and fill in the
gaps in measuring user experience. The newest addition to the metrics story are
two &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field metrics&lt;/a&gt;—&lt;a href=&quot;https://web.dev/lcp&quot;&gt;Largest
Contentful Paint (LCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/cls&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;—which
are being incubated in W3C Web Performance Working Group, and a new &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab
metric&lt;/a&gt;—&lt;a href=&quot;https://web.dev/tbt&quot;&gt;Total Blocking Time
(TBT)&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;largest-contentful-paint-(lcp)&quot;&gt;Largest Contentful Paint (LCP) &lt;a class=&quot;w-headline-link&quot; href=&quot;#largest-contentful-paint-(lcp)&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt; reports the time when the largest
content element becomes visible in the viewport.&lt;/p&gt;
&lt;p&gt;Before Largest Contentful Paint, &lt;a href=&quot;https://web.dev/first-meaningful-paint/&quot;&gt;First Meaningful Paint
(FMP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/speed-index/&quot;&gt;Speed Index (SI)&lt;/a&gt; served to
capture the loading experience after the initial paint, but these metrics are
complex and often do not identify when the main content of the page has loaded.
Research has shown that simply looking at when &lt;a href=&quot;https://web.dev/lcp/#examples&quot;&gt;the largest element on the
page&lt;/a&gt; is rendered better represents when the main content of a
page is loaded.&lt;/p&gt;
&lt;p&gt;The new Largest Contentful Paint metric will soon be available in Lighthouse
reports and in the meantime you can &lt;a href=&quot;https://web.dev/lcp/#measure-lcp-in-javascript&quot;&gt;measure LCP in
JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;total-blocking-time-(tbt)&quot;&gt;Total Blocking Time (TBT) &lt;a class=&quot;w-headline-link&quot; href=&quot;#total-blocking-time-(tbt)&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/tbt/&quot;&gt;Total Blocking Time (TBT)&lt;/a&gt; metric measures the total amount of time
between &lt;a href=&quot;https://web.dev/first-contentful-paint/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/interactive/&quot;&gt;Time to
Interactive (TTI)&lt;/a&gt; where the main thread was blocked for long
enough to prevent input responsiveness.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://web.dev/custom-metrics/#long-tasks-api&quot;&gt;task is considered long&lt;/a&gt; if it runs on the
main thread for more than 50 milliseconds. Any millisecond over that is counted
towards that task&#39;s blocking time.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/speed-tooling-evolutions-cds-2019/tbt.png&quot; alt=&quot;A diagram representing a 150 millisecond task which has 100 miliseconds of blocking time.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;The Total Blocking Time for a page is the sum of the blocking times of all long
tasks that occured between FCP and TTI.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/speed-tooling-evolutions-cds-2019/tbt2.png&quot; alt=&quot;A diagram representing a five tasks with 60 miliseconds of total blocking time out of 270 milliseconds of main thread time.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;While Time to Interactive does a good job of identifying when the main thread
calms down later in load, Total Blocking Time aims to quantify how strained the
main thread is throughout load. This way, TTI and TBT complement each other and
provide balance.&lt;/p&gt;
&lt;h3 id=&quot;cumulative-layout-shift-(cls)&quot;&gt;Cumulative Layout Shift (CLS) &lt;a class=&quot;w-headline-link&quot; href=&quot;#cumulative-layout-shift-(cls)&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt; measures visual stability of a page and
quantifies how often users experience unexpected layout shifts. Unexpected
movement of content can be very frustrating and this new metric helps you
address that problem by measuring how often it&#39;s occurring for your users.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video autoplay=&quot;&quot; controls=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability-poster.png&quot; width=&quot;658&quot; height=&quot;510&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability2.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability2.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
A screencast illustrating how layout instability can negatively affect
users.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://web.dev/cls&quot;&gt;detailed guide to Cumulative Layout Shift&lt;/a&gt; to learn how
it&#39;s calculated and how to measure it.&lt;/p&gt;
&lt;p&gt;The new Lighthouse performance score formula will soon de-emphasize FMP and FCI
and include the three new metrics—LCP, TBT, and CLS—as they better capture when
a page feels usable.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/speed-tooling-evolutions-cds-2019/performance-metrics.png&quot; alt=&quot;In Lighthouse v6 First Contentful Paint, Speed Index, and Largest
Contentful Paint are the main load performance metrics; Time To Interactive,
First Input Delay, Max Potential First Input Delay, and Total Blocking Time are
the main interactivity metrics; And Cumulative Layout Shift is the main
predictability metric.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://web.dev/performance-scoring/&quot;&gt;Lighthouse performance scoring&lt;/a&gt; and the new
&lt;a href=&quot;https://web.dev/metrics/&quot;&gt;web.dev metrics collection&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;h2 id=&quot;field-data-(crux)-thresholds-adjusted-in-pagespeed-insights&quot;&gt;Field data (CrUX) thresholds adjusted in PageSpeed Insights &lt;a class=&quot;w-headline-link&quot; href=&quot;#field-data-(crux)-thresholds-adjusted-in-pagespeed-insights&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Over the past year we have been analyzing &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;web performance from the
field&lt;/a&gt; via &lt;a href=&quot;https://developers.google.com/web/tools/chrome-user-experience-report&quot;&gt;Chrome User
Experience&lt;/a&gt;
(CrUX) data. With insights from that data we reassessed the thresholds that we
use to label a website &amp;quot;slow&amp;quot;, &#39;moderate&amp;quot;, or &amp;quot;fast&amp;quot; in field performance.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The term &amp;quot;average&amp;quot; that used to describe sites that are in between
&amp;quot;slow&amp;quot; and &amp;quot;fast&amp;quot; is now changed to &amp;quot;moderate&amp;quot; which is more fitting since this
middle group was not related to a statistical average.&lt;/p&gt;
&lt;/div&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/speed-tooling-evolutions-cds-2019/crux-data.png&quot; alt=&quot;Two bar charts showing
the distribution of slow, fast, and moderate speed for FCP and FID.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;In order to get an overall assessment for a site, &lt;a href=&quot;https://developers.google.com/speed/pagespeed/insights&quot;&gt;PageSpeed Insights
(PSI)&lt;/a&gt; uses a certain
percentile of the total distribution of field data as the golden number for that
site; the previous thresholds used were 90th percentile for First Contentful
Paint and 95th percentile for First Input Delay (FID).&lt;/p&gt;
&lt;p&gt;For example, if a site has an FCP distribution of 50% fast, 30% moderate, 20%
slow, the 90th percentile FCP is in the slow section, making the overall field
score for the site slow.&lt;/p&gt;
&lt;p&gt;This has been adjusted to have a better overall distribution across websites and
the new breakdown is:&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;Metric&lt;/td&gt; &lt;td&gt;Overall Percentile&lt;/td&gt; &lt;td&gt;Fast (ms)&lt;/td&gt; &lt;td&gt;Moderate
(ms)&lt;/td&gt; &lt;td&gt;Slow (ms)&lt;/td&gt;
&lt;/tr&gt; &lt;tr&gt;
&lt;td&gt;FCP&lt;/td&gt; &lt;td&gt;75th percentile&lt;/td&gt; &lt;td&gt;1000&lt;/td&gt; &lt;td&gt;1000-3000&lt;/td&gt;
&lt;td&gt;3000+&lt;/td&gt;
&lt;/tr&gt; &lt;tr&gt;
&lt;td&gt;FID&lt;/td&gt; &lt;td&gt;95th percentile&lt;/td&gt; &lt;td&gt;100&lt;/td&gt; &lt;td&gt;100-300&lt;/td&gt;
&lt;td&gt;300+&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;For example, now if a site has an FCP distribution of 50% fast, 30% moderate,
20% slow, the 75th percentile FCP is in the moderate section, making the overall
field score for the site moderate.&lt;/p&gt;
&lt;h2 id=&quot;canonical-url-redirects-in-pagespeed-insights&quot;&gt;Canonical URL redirects in PageSpeed Insights &lt;a class=&quot;w-headline-link&quot; href=&quot;#canonical-url-redirects-in-pagespeed-insights&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To enable you to measure the user&#39;s experience as accurately as possible, the
PageSpeed Insights team has added a reanalyze prompt to PSI. For sites that are
redirected to a new URL, you&#39;re prompted to rerun the report on the landing URL
for a more complete picture of your actual performance.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/speed-tooling-evolutions-cds-2019/psi-reanalyze.png&quot; alt=&quot;PSI user interface
showing the URL redirect and the &#39;Reanalyze&#39; button&quot;&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;crux-in-the-new-search-console-speed-report&quot;&gt;CrUX in the new Search Console Speed report &lt;a class=&quot;w-headline-link&quot; href=&quot;#crux-in-the-new-search-console-speed-report&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Search Console rolled out their &lt;a href=&quot;https://webmasters.googleblog.com/2019/11/search-console-speed-report.html&quot;&gt;new Speed
report&lt;/a&gt;
a week before Chrome Dev Summit. It uses data from the Chrome User Experience
Report to help site owners discover potential user experience problems. The
Speed report automatically assigns groups of similar URLs into &amp;quot;Fast&amp;quot;,
&amp;quot;Moderate,&amp;quot; and &amp;quot;Slow&amp;quot; buckets, and helps prioritize performance improvements
for specific issues.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/speed-tooling-evolutions-cds-2019/search-console-speed-report.png&quot; alt=&quot;Search
Console Speed report.&quot;&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;web-almanac&quot;&gt;Web Almanac &lt;a class=&quot;w-headline-link&quot; href=&quot;#web-almanac&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/speed-tooling-evolutions-cds-2019/web-almanac-at-cds-2019.png&quot; alt=&quot;Dion Almaer
presenting Web Almanac at CDS 2019.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;In the opening keynote we announced the launch of the &lt;a href=&quot;https://almanac.httparchive.org/en/2019/&quot;&gt;Web
Almanac&lt;/a&gt;, an annual project that
matches the stats and trends about the state of the web with the expertise of
the web community. 85 contributors, made up of Chrome developers and the web
community, have volunteered to work on the project, which analyzes 20 core
aspects about the web addressing how sites are built, delivered, and
experienced. Start exploring the Web Almanac to learn more about the state of &lt;a href=&quot;https://almanac.httparchive.org/en/2019/performance&quot;&gt;performance&lt;/a&gt;, &lt;a href=&quot;https://almanac.httparchive.org/en/2019/javascript&quot;&gt;JavaScript&lt;/a&gt;, and &lt;a href=&quot;https://almanac.httparchive.org/en/2019/third-parties&quot;&gt;third-party&lt;/a&gt; code on the web.&lt;/p&gt;
&lt;h2 id=&quot;learn-more&quot;&gt;Learn more &lt;a class=&quot;w-headline-link&quot; href=&quot;#learn-more&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For more details about performance tooling updates from
Chrome Developer Summit, watch the Speed tooling evolutions talk:&lt;/p&gt;
&lt;div class=&quot;w-youtube&quot;&gt;
&lt;iframe class=&quot;w-youtube__embed&quot; src=&quot;https://www.youtube.com/embed/iaWLXf1FgI0&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
</content>
<author>
<name>Elizabeth Sweeny</name>
</author>
</entry>
<entry>
<title>Lighthouse evolution: continuous integration, new performance score formula, and more</title>
<link href="https://web.dev/lighthouse-evolution-cds-2019/"/>
<updated>2019-12-15T16:00:00-08:00</updated>
<id>https://web.dev/lighthouse-evolution-cds-2019/</id>
<content type="html">&lt;p&gt;In the &lt;a href=&quot;https://youtu.be/iaWLXf1FgI0&quot;&gt;Speed Tooling Evolutions&lt;/a&gt; talk at Chrome
Developer Summit (CDS), Paul Irish and I presented the newest products and
features coming from Google that can help you build and maintain an exceptionally fast experience for all your users. At the center of that story
are additions to the &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; family of performance monitoring tools.&lt;/p&gt;
&lt;h2 id=&quot;lighthouse-ci-alpha-release&quot;&gt;Lighthouse CI alpha release &lt;a class=&quot;w-headline-link&quot; href=&quot;#lighthouse-ci-alpha-release&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Lighthouse team has launched the alpha version of &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci&quot;&gt;Lighthouse
CI&lt;/a&gt; the new continuous
integration product that enables you to run Lighthouse on every commit before
pushing to production. Lighthouse CI runs Lighthouse multiple times, asserts
static audit or metric thresholds, and then uploads Lighthouse reports to a
server for visual diffing and basic category score history. Existing
&lt;a href=&quot;https://web.dev/use-lighthouse-for-performance-budgets&quot;&gt;budgets.json configurations&lt;/a&gt; work
seamlessly alongside the new expressive syntax for asserting &lt;em&gt;any&lt;/em&gt; Lighthouse
audit or category result.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/lighthouse-evolution-cds-2019/lighthouse-ci.png&quot; alt=&quot;Lighthouse CI report.&quot; style=&quot;max-width: 50%&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Lighthouse CI supports &lt;a href=&quot;https://travis-ci.com/&quot;&gt;Travis CI&lt;/a&gt;, &lt;a href=&quot;https://circleci.com/&quot;&gt;Circle
CI&lt;/a&gt;, and &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub
Actions&lt;/a&gt; out-of-the-box and any Ubuntu or
container-based CI service with some configuration. You can install the
Lighthouse CI server on-premise or use a &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci/blob/master/docs/recipes/docker-server/README.md&quot;&gt;docker image for instant
setup&lt;/a&gt;.
Free, public, temporary Lighthouse report storage is available as an alternative
to get started right away.&lt;/p&gt;
&lt;h2 id=&quot;coming-soon:-performance-score-updates&quot;&gt;Coming soon: Performance score updates &lt;a class=&quot;w-headline-link&quot; href=&quot;#coming-soon:-performance-score-updates&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Changes are coming to the Lighthouse Performance score version 6! In version 5
(as of November 2019), Lighthouse has five metrics that are &lt;a href=&quot;https://web.dev/performance-scoring/#weightings&quot;&gt;weighted and
blended&lt;/a&gt; to form the 0-100 Performance score:
&lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint&lt;/a&gt;, &lt;a href=&quot;https://web.dev/speed-index/&quot;&gt;Speed Index&lt;/a&gt;, &lt;a href=&quot;https://web.dev/first-meaningful-paint/&quot;&gt;First Meaningful
Paint&lt;/a&gt;, &lt;a href=&quot;https://web.dev/interactive/&quot;&gt;Time to Interactive&lt;/a&gt;, and
&lt;a href=&quot;https://web.dev/first-cpu-idle/&quot;&gt;First CPU Idle&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/lighthouse-evolution-cds-2019/lighthouse-performance-score.png&quot; alt=&quot;Comparison of Lighthouse performance score formulas in versions 5 and 6.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;See &lt;a href=&quot;https://web.dev/performance-scoring/&quot;&gt;Lighthouse performance scoring&lt;/a&gt; for detailed
information.&lt;/p&gt;
&lt;p&gt;In Lighthouse version 6, new metrics, &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;
and &lt;a href=&quot;https://web.dev/tbt/&quot;&gt;Total Blocking Time (TBT)&lt;/a&gt;, are replacing First CPU Idle (FCI) and
First Meaningful Paint (FMP). The weights of each of the five metrics will be
adjusted to better balance different phases of load and interactivity measures.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt; is another new metric that&#39;s
still being finessed and should become a part of the Lighthouse Performance
score eventually.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The Lighthouse team is still working to ensure that all scoring curves are
fine-tuned, and the metrics are mature and thoroughly tested. They aim to ship
the Lighthouse v6 Performance score in January 2020.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Performance engineers sometimes find speed tools results difficult
to reproduce due to two discrete challenges--variability and cross-environment
inconsistency. Variability is the issue of seeing numbers change even when the
testing environment remains the same. Cross-environment inconsistency is the
issue of getting different results when running tests on the same page, but in
differing environments (for example, DevTools and PageSpeed Insights). While the
Lighthouse team is working on ways to mitigate variability, it&#39;s helpful to
understand &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/variability#sources_of_variability&quot;&gt;sources of
variability&lt;/a&gt;
and &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/variability#strategies_for_dealing_with_variance&quot;&gt;how you can deal with
it&lt;/a&gt;.
The Lighthouse team is also investigating calibration methods to reduce
differences between environments, but it&#39;s fair to expect that different
conditions and hardware lead to different measurements—at least for now.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;lighthouse-stack-packs&quot;&gt;Lighthouse Stack Packs &lt;a class=&quot;w-headline-link&quot; href=&quot;#lighthouse-stack-packs&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lighthouse can automatically detect if sites use a framework or a &lt;a href=&quot;https://en.wikipedia.org/wiki/Content_management_system&quot;&gt;content
management system
(CMS)&lt;/a&gt; and include
stack-specific advice in the report. &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-stack-packs&quot;&gt;Stack
Packs&lt;/a&gt; add customized
recommendations, curated by community experts (like you!), on top of Lighthouse
report core audits.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/lighthouse-evolution-cds-2019/lighthouse-stack-packs.png&quot; alt=&quot;Lighthouse report recommendation for deferring offscreen images in React applications.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;At the moment, there are Stack Packs for Angular, WordPress, Magento, React, and
AMP. To create your own Stack Pack, visit the &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-stack-packs/blob/master/CONTRIBUTING.md&quot;&gt;GitHub
repo&lt;/a&gt;
or &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-stack-packs/issues&quot;&gt;contact the Lighthouse team&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;coming-soon:-lighthouse-plugins-as-chrome-extensions&quot;&gt;Coming soon: Lighthouse plugins as Chrome Extensions &lt;a class=&quot;w-headline-link&quot; href=&quot;#coming-soon:-lighthouse-plugins-as-chrome-extensions&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/lighthouse-evolution-cds-2019/lighthouse-plugin-icon.png&quot; alt=&quot;Lighthouse plugin icon.&quot; style=&quot;max-width: 250px;&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/blob/master/docs/plugins.md&quot;&gt;Lighthouse
Plugins&lt;/a&gt;
are another way you can take advantage of Lighthouse&#39;s extensibility. There
are a lot of quality checks that Lighthouse core audits currently don&#39;t cover,
either because they are only applicable to a subset of developers or because the
team hasn&#39;t had the bandwidth to create the audits yet.&lt;/p&gt;
&lt;p&gt;Lighthouse plugins allow community experts to implement a new set of checks that
Lighthouse can run and add to the report as a new category. Right now, plugins
only work in &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse#cli&quot;&gt;Lighthouse
CLI&lt;/a&gt;, but the goal is to
enable running them in the DevTools &lt;strong&gt;Audits&lt;/strong&gt; panel too.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/lighthouse-evolution-cds-2019/lighthouse-plugin-devtools.png&quot; alt=&quot;Chrome DevTools Audits panel with options for running Lighthouse plugins for Google Publisher Ads and User Experience.&quot; style=&quot;max-width: 400px;&quot;&gt;
&lt;figcaption&gt;Community Plugins in DevTools Audits panel (beta)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When users install Lighthouse plugin extensions from the &lt;a href=&quot;https://chrome.google.com/webstore/category/extensions&quot;&gt;Chrome Web
Store&lt;/a&gt;, DevTools will
identify installed plugins and offer them as an option in the &lt;strong&gt;Audits&lt;/strong&gt; panel.
The Lighthouse team will be building the support for the plugin approach in the
coming months, so stay tuned. In the meantime, you can create a plugin today as
a node module and make it accessible to all Lighthouse users via the CLI!&lt;/p&gt;
&lt;h2 id=&quot;learn-more&quot;&gt;Learn more &lt;a class=&quot;w-headline-link&quot; href=&quot;#learn-more&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For more details about Lighthouse and other performance tooling updates from
CDS 2019, watch the Speed tooling evolutions talk:&lt;/p&gt;
&lt;div class=&quot;w-youtube&quot;&gt;
&lt;iframe class=&quot;w-youtube__embed&quot; src=&quot;https://www.youtube.com/embed/iaWLXf1FgI0&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Your feedback is invaluable in making Lighthouse better, so go on and try out
&lt;a href=&quot;http://bit.ly/lhci&quot;&gt;Lighthouse CI&lt;/a&gt;, write a &lt;a href=&quot;http://bit.ly/lh-stackpacks&quot;&gt;Stack
Pack&lt;/a&gt;, or create a &lt;a href=&quot;http://bit.ly/lh-plugins&quot;&gt;Lighthouse
Plugin&lt;/a&gt; and &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/issues&quot;&gt;let us
know&lt;/a&gt; what you think.&lt;/p&gt;
</content>
<author>
<name>Elizabeth Sweeny</name>
</author>
</entry>
<entry>
<title>Adaptive loading: improving web performance on slow devices</title>
<link href="https://web.dev/adaptive-loading-cds-2019/"/>
<updated>2019-12-15T16:00:00-08:00</updated>
<id>https://web.dev/adaptive-loading-cds-2019/</id>
<content type="html">&lt;p&gt;Device capabilities and network connections vary a lot. Sites that delight users
on high-end devices can be
&lt;a href=&quot;https://v8.dev/blog/cost-of-javascript-2019&quot;&gt;unusable&lt;/a&gt; on low-end ones. Sites
that load smoothly on fast networks can come to a halt on slow ones. Any user
can experience a slow website, that&#39;s why developing &amp;quot;one-size fits all&amp;quot;
solutions may not always work.&lt;/p&gt;
&lt;p&gt;In their &lt;a href=&quot;https://www.youtube.com/watch?v=puUPpVrIRkc&quot;&gt;Chrome Dev Summit talk&lt;/a&gt;,
Addy Osmani from Google and Nate Schloss from Facebook explore a solution to that problem—a
pattern for delivering pages that better cater to a variety of user
constraints. They call it &lt;em&gt;&lt;strong&gt;adaptive loading&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-adaptive-loading&quot;&gt;What is adaptive loading? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-is-adaptive-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adaptive loading involves delivering different experiences to different users
based on their network and hardware constraints, specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A fast core experience for all users (including low-end devices).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Progressively adding high-end-only features, if a user&#39;s network and hardware
can handle it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By optimizing for specific hardware and network constraints you enable every
user to get the best possible experience for their device. Tailoring the
experience to users&#39; constraints can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Serving low-quality images and videos on slow networks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Throttling the frame-rate of animations on low-end devices.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Avoiding computationally expensive operations on low-end devices.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Blocking third-party scripts on slower devices.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Loading non-critical JavaScript for interactivity only on fast CPUs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-implement-adaptive-loading&quot;&gt;How to implement adaptive loading &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-to-implement-adaptive-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The signals you can use for adaptive loading are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Network—for fine-tuning data transfer to use less bandwidth (via
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType&quot;&gt;&lt;code&gt;navigator.connection.effectiveType&lt;/code&gt;&lt;/a&gt;).
You can also leverage the user&#39;s Data Saver preferences (via
&lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/save-data#detecting_the_save-data_setting&quot;&gt;&lt;code&gt;navigator.connection.saveData&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Memory—for reducing memory consumption on low-end devices (via
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory&quot;&gt;&lt;code&gt;navigator.deviceMemory&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CPU core count—for limiting costly JavaScript execution and reducing CPU
intensive logic when a device can&#39;t handle it well (via
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency&quot;&gt;&lt;code&gt;navigator.hardwareConcurrency&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are two places where you can make a decision about what to serve to users:
the client and the server. On the client, you have the JavaScript APIs noted
above. On the server, you can use &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/client-hints&quot;&gt;client
hints&lt;/a&gt;
to get insight into the user&#39;s device capabilities and the network they&#39;re
connected to.&lt;/p&gt;
&lt;h3 id=&quot;adaptive-loading-in-react&quot;&gt;Adaptive loading in React &lt;a class=&quot;w-headline-link&quot; href=&quot;#adaptive-loading-in-react&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/react-adaptive-hooks&quot;&gt;React Adaptive Loading Hooks &amp;amp;
Utilities&lt;/a&gt; is a suite
for the React ecosystem that makes it easier to adapt your sites to lower-end
devices. It includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;useNetworkStatus()&lt;/code&gt; hook for adapting based on network status (&lt;code&gt;slow-2g&lt;/code&gt;,
&lt;code&gt;2g&lt;/code&gt;, &lt;code&gt;3g&lt;/code&gt;, or &lt;code&gt;4g&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;useSaveData()&lt;/code&gt; hook for adapting based on the user&#39;s Data Saver
preferences.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;useHardwareConcurrency()&lt;/code&gt; hook for adapting based on the number of
logical CPU processor cores on the user&#39;s device.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;useMemoryStatus()&lt;/code&gt; hook for adapting based on the user&#39;s device memory
(RAM).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each hook accepts an optional argument for setting the initial value. This
option is useful in two scenarios: when the user&#39;s browser does not support the
relevant API and for server-side rendering where you can use the client hint
data to set the initial value on the server. For example, the
&lt;code&gt;useNetworkStatus()&lt;/code&gt; hook can use the initial value passed from client hint for
server-side rendering and, when executed on the client, update itself if the
network effective type changes.&lt;/p&gt;
&lt;p&gt;React Adaptive Loading Hooks &amp;amp; Utilities are implemented using web platform APIs
(&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API&quot;&gt;Network
Information&lt;/a&gt;,
&lt;a href=&quot;https://developers.google.com/web/updates/2017/12/device-memory&quot;&gt;Device Memory&lt;/a&gt;
and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency&quot;&gt;Hardware
Concurrency&lt;/a&gt;).
You can use the same APIs to apply adaptive loading concepts to other frameworks
and libraries, such as
&lt;a href=&quot;https://netbasal.com/connection-aware-components-in-angular-3a66bb0bab6f&quot;&gt;Angular&lt;/a&gt;,
&lt;a href=&quot;https://dev.to/vorillaz/serving-adaptive-components-using-the-network-information-api-lbo&quot;&gt;Vue&lt;/a&gt;,
and others.&lt;/p&gt;
&lt;h2 id=&quot;adaptive-loading-in-action&quot;&gt;Adaptive loading in action &lt;a class=&quot;w-headline-link&quot; href=&quot;#adaptive-loading-in-action&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This section explores demos of how you could use adaptive loading and real-world
examples from sites such as Facebook, eBay, Tinder, and others.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://adaptive-loading.web.app/react-movie-network-aware-loading/&quot;&gt;React
Movie&lt;/a&gt; demo
shows how to &lt;a href=&quot;https://github.com/GoogleChromeLabs/adaptive-loading/tree/master/react-movie-network-aware-loading&quot;&gt;adapt media serving based on the network
status&lt;/a&gt;.
It&#39;s an application for browsing movies that shows posters, summaries, and cast
lists. Based on the user&#39;s effective connection type, it serves high-quality
posters on fast connections and low-quality posters on slow ones.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/twittersupport/status/1047607749708668928&quot;&gt;Twitter has a Data Saver
mode&lt;/a&gt; designed to
reduce the amount of data used. In this mode, preview images load in
low-resolution and large images load only when you tap on the preview. With this
option enabled, users on iOS and Android saved 50% in data-usage from images,
and users on the web saved 80%. Here&#39;s a React
&lt;a href=&quot;https://github.com/GoogleChromeLabs/adaptive-loading/tree/master/react-twitter-save-data-loading(hook)&quot;&gt;demo&lt;/a&gt;
that uses the Save Data hook to replicate the Twitter timeline. Try
opening your DevTools &lt;strong&gt;Network&lt;/strong&gt; panel and looking at the difference in the amount
of data transferred as you scroll while Save Data is disabled versus when it&#39;s
enabled.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/adaptive-loading-cds-2019/twitter-save-data.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
A screencast comparing scrolling the Twitter timeline with Data Saver on and off. With Data Saver on, only image previews are loaded and videos don&#39;t autoplay.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;eBay conditionally turns on and off features like zooming when a user&#39;s hardware
or network conditions don&#39;t support them well. You can achieve this through
adaptive &lt;a href=&quot;https://web.dev/reduce-javascript-payloads-with-code-splitting/&quot;&gt;code-splitting&lt;/a&gt; and
code loading—a way to conditionally load more highly interactive components or
run more computationally heavy operations on high-end devices, while not sending
those scripts down to users on slower devices. Check out the video at &lt;a href=&quot;https://youtu.be/puUPpVrIRkc?t=973&quot;&gt;16
mins&lt;/a&gt; where Addy shows this pattern
implemented with &lt;a href=&quot;https://web.dev/code-splitting-suspense/&quot;&gt;React.lazy() and Suspense&lt;/a&gt; on a
&lt;a href=&quot;https://github.com/GoogleChromeLabs/adaptive-loading/tree/master/react-ebay-network-aware-code-splitting&quot;&gt;demo eBay product
page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/adaptive-loading-cds-2019/adaptive-code-splitting.png&quot; alt=&quot;A diagram of modules shipped for a product page on low-end and high-enddevices: both versions include &amp;quot;image viewer&amp;quot;, while the high-end versionincludes additional &amp;quot;zoom&amp;quot; and &amp;quot;carousel&amp;quot; modules.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tinder is using a number of adaptive loading patterns in its
&lt;a href=&quot;https://medium.com/@addyosmani/a-tinder-progressive-web-app-performance-case-study-78919d98ece0&quot;&gt;web&lt;/a&gt;
and &lt;a href=&quot;https://blog.gotinder.com/introducing-tinder-lite/&quot;&gt;Lite app&lt;/a&gt; to keep the
experience fast for everyone. If a user is on a slow network or has Data Saver
enabled, they disable video autoplay, limit &lt;a href=&quot;https://web.dev/link-prefetch/&quot;&gt;route prefetching&lt;/a&gt;
and limit loading the next image in the carousel to loading images one at a time
as users swipe. After implementing these optimizations, they&#39;ve seen significant
improvements in average swipe count in countries such as Indonesia.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/adaptive-loading-cds-2019/tinder.png&quot; style=&quot;max-width: 75%&quot; alt=&quot;A screenshot of two versions of Tinder chat: with autoplaying video and
with a video with play button overlay. A screenshot of a Tinder profile with
caption &#39;Limit carousel images on Data Saver or 3G&#39;.
A code snippet for prefetching in-viewport videos only on 4G.&quot;&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;adaptive-loading-at-facebook&quot;&gt;Adaptive loading at Facebook &lt;a class=&quot;w-headline-link&quot; href=&quot;#adaptive-loading-at-facebook&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One issue that comes up in adaptive loading is grouping devices into high-end
and low-end classes based on available signals. On mobile devices the
&lt;a href=&quot;https://developer.chrome.com/multidevice/user-agent&quot;&gt;user-agent (UA)&lt;/a&gt; string
provides the device name which enables Facebook to use publicly available data
on device characteristics to group mobile devices into classes. However, on
desktop devices the only relevant information the UA provides is the device&#39;s
operating system.&lt;/p&gt;
&lt;p&gt;For grouping desktop devices, Facebook logs the data about the operating system,
CPU cores (from &lt;code&gt;navigator.hardwareConcurrency&lt;/code&gt;), and RAM memory
(&lt;code&gt;navigator.deviceMemory&lt;/code&gt;) in their performance monitoring. Looking at the
relationships between different types of hardware and performance, they
classified devices into five categories. With hardware classes integrated into
performance monitoring, they get a more complete picture of how people use
Facebook products depending on their device and can identify regressions more
easily.&lt;/p&gt;
&lt;p&gt;Check out the video at &lt;a href=&quot;https://youtu.be/puUPpVrIRkc?t=1443&quot;&gt;24 mins&lt;/a&gt;, where
Nate walks through how Facebook approaches device grouping and uses adaptive
loading for animations and loading JavaScript.&lt;/p&gt;
&lt;h2 id=&quot;learn-more-about-adaptive-loading&quot;&gt;Learn more about adaptive loading &lt;a class=&quot;w-headline-link&quot; href=&quot;#learn-more-about-adaptive-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adaptive loading is all about designing your sites with inclusivity in mind.
Build a core experience that works great for everyone, then toggle or layer
features that make it even more awesome if a user has enough memory, CPU, or a
fast network. To learn more about adaptive loading, check out the available
&lt;a href=&quot;https://github.com/GoogleChromeLabs/adaptive-loading#full-applications&quot;&gt;demos&lt;/a&gt;
and watch the Chrome Dev Summit talk:&lt;/p&gt;
&lt;div class=&quot;w-youtube&quot;&gt;
&lt;iframe class=&quot;w-youtube__embed&quot; src=&quot;https://www.youtube.com/embed/puUPpVrIRkc&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
</content>
<author>
<name>Milica Mihajlija</name>
</author>
</entry>
<entry>
<title>Indexing your offline-capable pages with the Content Indexing API</title>
<link href="https://web.dev/content-indexing-api/"/>
<updated>2019-12-11T16:00:00-08:00</updated>
<id>https://web.dev/content-indexing-api/</id>
<content type="html">&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The Content Indexing API begins an origin trial in Chrome 80 as part of Chrome&#39;s
&lt;a href=&quot;https://developers.google.com/web/updates/capabilities&quot;&gt;Capabilities project&lt;/a&gt;.
This post will be updated as the implementation progresses.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;what&quot;&gt;What is the Content Indexing API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using a &lt;a href=&quot;https://developers.google.com/web/progressive-web-apps&quot;&gt;progressive web
app&lt;/a&gt; means having access
to information people care about—images, videos, articles, and more—regardless
of the current state of your network connection. Technologies like &lt;a href=&quot;https://developers.google.com/web/fundamentals/primers/service-workers&quot;&gt;service
workers&lt;/a&gt;,
the &lt;a href=&quot;https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api&quot;&gt;Cache Storage
API&lt;/a&gt;,
and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API&quot;&gt;IndexedDB&lt;/a&gt;
provide you with the building blocks for storing and serving data when folks
interact directly with a PWA. But building a high-quality, offline-first PWA is
only part of the story. If folks don&#39;t realize that a web app&#39;s content is
available while they&#39;re offline, they won&#39;t take full advantage of the work you
put into implementing that functionality.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;discovery&lt;/strong&gt; problem; how can your PWA make users aware of its
offline-capable content so that they can discover and view what&#39;s available? The
Content Indexing API is the currently proposed solution to this problem that the
Chrome team is experimenting with. The developer-facing portion of this solution
is an extension to service workers, which allows developers to add URLs and
metadata of offline-capable pages to a local index maintained by the browser.&lt;/p&gt;
&lt;p&gt;Once the index is populated with content from your PWA, as well as any other
installed PWAs, it will be surfaced by the browser. Experiments are also being
run to determine how and where this offline content listing will be presented,
and the initial plans include a dedicated area of Chrome for Android&#39;s &lt;strong&gt;Downloads&lt;/strong&gt;
page:&lt;/p&gt;
&lt;div class=&quot;w-columns&quot;&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/content-indexing-api/downloads-menu.png&quot; alt=&quot;A screenshot of the Downloads menu item on Chrome&#39;s new tab page.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
First, select the &lt;b&gt;Downloads&lt;/b&gt; menu item on Chrome&#39;s new tab page.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/content-indexing-api/articles-for-you.png&quot; alt=&quot;Media and articles that have been added to the index.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Media and articles that have been added to the index will be shown in the
&lt;b&gt;Articles for You&lt;/b&gt; section.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Additionally, Chrome is running experiments to proactively recommend this
content when it detects that a user is offline.&lt;/p&gt;
&lt;p&gt;The Content Indexing API &lt;strong&gt;is not an alternative way of caching content&lt;/strong&gt;. It&#39;s
a way of providing metadata about pages that are already cached by your service
worker, so that the browser can surface those pages when folks are likely to
want to view them. The Content Indexing API helps with &lt;strong&gt;discoverability&lt;/strong&gt; of
cached pages.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The Content Indexing API is not a searchable index. While you can get a list
of all indexed entries, there&#39;s no way to query against indexed metadata
directly.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;status&quot;&gt;Current status &lt;a class=&quot;w-headline-link&quot; href=&quot;#status&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Create explainer&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/rayankans/content-index&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Create initial draft of specification&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://rayankans.github.io/content-index/spec/&quot;&gt;In progress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Gather feedback &amp;amp; iterate on design&lt;/td&gt;
&lt;td&gt;In progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Origin trial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Started in Chrome 80&lt;/strong&gt; &lt;br&gt; Expected to run through Chrome 82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Launch&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;see-it-in-action&quot;&gt;See it in action &lt;a class=&quot;w-headline-link&quot; href=&quot;#see-it-in-action&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The best way to get a feel for the Content Indexing API is to try a sample
application.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make sure that you&#39;re using a supported browser and platform. Currently,
that&#39;s limited to &lt;strong&gt;Chrome 80 or later on Android&lt;/strong&gt;. Go to &lt;code&gt;chrome://version&lt;/code&gt; to see
what version of Chrome you&#39;re running.&lt;/li&gt;
&lt;li&gt;Visit &lt;a href=&quot;https://contentindex.dev/&quot;&gt;https://contentindex.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click the &lt;code&gt;+&lt;/code&gt; button next to one or more of the items on the list.&lt;/li&gt;
&lt;li&gt;(Optional) Disable your device&#39;s Wi-Fi and cellular data connection, or enable
airplane mode to simulate taking your browser offline.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Downloads&lt;/strong&gt; from Chrome&#39;s menu, and switch to the &lt;strong&gt;Articles for You&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Browse through the content that you previously saved.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can view &lt;a href=&quot;https://github.com/rayankans/contentindex.dev&quot;&gt;the source of the sample application on
GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another sample application, a &lt;a href=&quot;https://scrapbook-pwa.web.app/&quot;&gt;Scrapbook PWA&lt;/a&gt;,
illustrates the use of the Content Indexing API with the &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web
Share Target API&lt;/a&gt;. The &lt;a href=&quot;https://github.com/GoogleChrome/samples/blob/gh-pages/web-share/src/js/contentIndexing.js&quot;&gt;code demonstrates a
technique&lt;/a&gt;
for keeping the Content Indexing API in sync with items stored by a web app
using the &lt;a href=&quot;https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api&quot;&gt;Cache Storage API&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;using-the-api&quot;&gt;Using the API &lt;a class=&quot;w-headline-link&quot; href=&quot;#using-the-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use the API your app must have a service worker and URLs that are navigable
offline. If your web app does not currently have a service worker, the &lt;a href=&quot;https://developers.google.com/web/tools/workbox/&quot;&gt;Workbox
libraries&lt;/a&gt; can simplify
creating one.&lt;/p&gt;
&lt;h3 id=&quot;offline-capable-urls&quot;&gt;What type of URLs can be indexed as offline-capable? &lt;a class=&quot;w-headline-link&quot; href=&quot;#offline-capable-urls&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The API supports indexing URLs corresponding to HTML documents. A URL for a cached
media file, for example, can&#39;t be indexed directly. Instead, you need to provide
a URL for a page that displays media, and which works offline.&lt;/p&gt;
&lt;p&gt;A recommended pattern is to create a &amp;quot;viewer&amp;quot; HTML page that could accept the
underlying media URL as a query parameter and then display the contents of the
file, potentially with additional controls or content on the page.&lt;/p&gt;
&lt;p&gt;Web apps can only add URLs to the content index that are under the
&lt;a href=&quot;https://developers.google.com/web/ilt/pwa/introduction-to-service-worker&quot;&gt;scope&lt;/a&gt;
of the current service worker. In other words, a web app could not add a URL
belonging to a completely different domain into the content index.&lt;/p&gt;
&lt;h3 id=&quot;api-overview&quot;&gt;Overview &lt;a class=&quot;w-headline-link&quot; href=&quot;#api-overview&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Content Indexing API supports three operations: adding, listing, and
removing metadata. These methods are exposed from a new property, &lt;code&gt;index&lt;/code&gt;, that
has been added to the
&lt;code&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration&quot;&gt;ServiceWorkerRegistration&lt;/a&gt;&lt;/code&gt;
interface.&lt;/p&gt;
&lt;p&gt;The first step in indexing content is getting a reference to the current
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration&quot;&gt;&lt;code&gt;ServiceWorkerRegistration&lt;/code&gt;&lt;/a&gt;. Using &lt;code&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/ready&quot;&gt;navigator.serviceWorker.ready&lt;/a&gt;&lt;/code&gt; is the most straightforward way:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Remember to feature-detect before using the API:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;index&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Your Content Indexing API code goes here!&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;If you&#39;re making calls to the Content Indexing API from within a service worker,
rather than inside a web page, you can refer to the &lt;code&gt;ServiceWorkerRegistration&lt;/code&gt;
directly via &lt;code&gt;registration&lt;/code&gt;. It will &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/registration&quot;&gt;already be
defined&lt;/a&gt;
as part of the &lt;code&gt;ServiceWorkerGlobalScope.&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;adding-items&quot;&gt;Adding to the index &lt;a class=&quot;w-headline-link&quot; href=&quot;#adding-items&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Use the &lt;code&gt;add()&lt;/code&gt; method to index URLs and their associated metadata. It&#39;s up to
you to choose when items are added to the index. You might want to add to the
index in response to an input, like clicking a &amp;quot;save offline&amp;quot; button. Or you
might add items automatically each time cached data is updated via a mechanism
like &lt;a href=&quot;https://developers.google.com/web/updates/2019/08/periodic-background-sync&quot;&gt;periodic background
sync&lt;/a&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Required; set to something unique within your web app.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;article-123&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Required; this URL needs to be an offline-capable HTML page.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; launchUrl&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/articles/123&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Required; used in user-visible lists of content.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Article title&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Required; used in user-visible lists of content.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; description&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Amazing article about things!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Required; used in user-visible lists of content.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; icons&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; src&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/img/article-123.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; sizes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;64x64&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Optional; valid categories are currently:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// &#39;homepage&#39;, &#39;article&#39;, &#39;video&#39;, &#39;audio&#39;, or &#39;&#39; (default).&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; category&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;article&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Adding an entry only affects the content index; it does not add anything to the
&lt;a href=&quot;https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api&quot;&gt;Cache Storage
API&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;edge-case:-call-add()-from-window-context-if-your-icons-rely-on-a-fetch-handler&quot;&gt;Edge case: Call &lt;code&gt;add()&lt;/code&gt; from &lt;code&gt;window&lt;/code&gt; context if your icons rely on a &lt;code&gt;fetch&lt;/code&gt; handler &lt;a class=&quot;w-headline-link&quot; href=&quot;#edge-case:-call-add()-from-window-context-if-your-icons-rely-on-a-fetch-handler&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When you call &lt;code&gt;add()&lt;/code&gt;, Chrome will make a request for
each icon&#39;s URL to ensure that it has a copy of the icon to use when
displaying a list of indexed content.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you call &lt;code&gt;add()&lt;/code&gt; from the &lt;code&gt;window&lt;/code&gt; context (in other words, from your web
page), this request will trigger a &lt;code&gt;fetch&lt;/code&gt; event on your service worker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you call &lt;code&gt;add()&lt;/code&gt; within your service worker (perhaps inside another event
handler), the request will &lt;strong&gt;not&lt;/strong&gt; trigger the service worker&#39;s &lt;code&gt;fetch&lt;/code&gt; handler.
The icons will be fetched directly, without any service worker involvement. Keep
this in mind if your icons rely on your &lt;code&gt;fetch&lt;/code&gt; handler, perhaps because they
only exist in the local cache and not on the network. If they do, make sure that
you only call &lt;code&gt;add()&lt;/code&gt; from the &lt;code&gt;window&lt;/code&gt; context.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;listing-items&quot;&gt;Listing the index&#39;s contents &lt;a class=&quot;w-headline-link&quot; href=&quot;#listing-items&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;getAll()&lt;/code&gt; method returns a promise for an iterable list of indexed entries
and their metadata. Returned entries will contain all of the data saved with
&lt;code&gt;add()&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// entry.id, entry.launchUrl, etc. are all exposed.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;removing-items&quot;&gt;Removing items from the index &lt;a class=&quot;w-headline-link&quot; href=&quot;#removing-items&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To remove an item from the index, call &lt;code&gt;delete()&lt;/code&gt; with the &lt;code&gt;id&lt;/code&gt; of the item to
remove:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;article-123&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Calling &lt;code&gt;delete()&lt;/code&gt; only affects the index. It does not
delete anything from the &lt;a href=&quot;https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api&quot;&gt;Cache Storage
API&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--warning&quot;&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt;
Once indexed, entries are not automatically expired. It&#39;s
up to you to either present an interface in your web app for clearing out
entries, or periodically remove older entries that you know should no longer be
available offline.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;handling-contentdelete&quot;&gt;Handling a user delete event &lt;a class=&quot;w-headline-link&quot; href=&quot;#handling-contentdelete&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When the browser displays the indexed content, it may include its own user
interface with a &lt;strong&gt;Delete&lt;/strong&gt; menu item, giving people a chance to indicate that
they&#39;re done viewing previously indexed content. This is how the deletion
interface looks in Chrome 80:&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/content-indexing-api/delete-menu.png&quot; alt=&quot;The delete menu item.&quot; width=&quot;550&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;When someone selects that menu item, your web app&#39;s service worker will receive
a &lt;code&gt;contentdelete&lt;/code&gt; event. While handling this event is optional, it provides a
chance for your service worker to &amp;quot;clean up&amp;quot; content, like locally cached media
files, that someone has indicated they are done with.&lt;/p&gt;
&lt;p&gt;You do not need to call &lt;code&gt;registration.index.delete()&lt;/code&gt; inside your
&lt;code&gt;contentdelete&lt;/code&gt; handler; if the event has been fired, the relevant index
deletion has already been performed by the browser.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;contentdelete&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// event.id will correspond to the id value used&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// when the indexed content was added.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Use that value to determine what content, if any,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// to delete from wherever your app stores it—usually&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// the Cache Storage API or perhaps IndexedDB.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The &lt;code&gt;contentdelete&lt;/code&gt; event is only fired when the deletion happens due to
interaction with the browser&#39;s built-in user interface. It is &lt;em&gt;not&lt;/em&gt; fired when
&lt;code&gt;registration.index.delete()&lt;/code&gt; is called. If your web app triggers the index
deletion using that API method, it should also take care of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete&quot;&gt;cleaning up cached
content&lt;/a&gt; at the
same time.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;origin-trial&quot;&gt;Enabling support during the Origin Trial &lt;a class=&quot;w-headline-link&quot; href=&quot;#origin-trial&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Starting in Chrome 80, the API is available as an Origin Trial on Chrome for Android.&lt;/p&gt;
&lt;p&gt;Origin trials allow you to try new features and give feedback on their
usability, practicality, and effectiveness to the web standards community. For
more information, see the &lt;a href=&quot;https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md&quot;&gt;Origin Trials Guide for Web Developers&lt;/a&gt;.
To sign up for this or another origin trial, visit the &lt;a href=&quot;https://developers.chrome.com/origintrials/#/trials/active&quot;&gt;registration page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To participate in an origin trial:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/85568392920039425&quot;&gt;Request a token&lt;/a&gt; for your origin.&lt;/li&gt;
&lt;li&gt;Add the token to your pages. There are two ways to do that:
&lt;ul&gt;
&lt;li&gt;Add an &lt;code&gt;origin-trial&lt;/code&gt; &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag to the head of each page. For example,
this may look something like: &lt;br&gt;
&lt;code&gt;&amp;lt;meta http-equiv=&amp;quot;origin-trial&amp;quot; content=&amp;quot;TOKEN_GOES_HERE&amp;quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If you can configure your server, you can also add the token
using an &lt;code&gt;Origin-Trial&lt;/code&gt; HTTP header. The resulting response header should
look something like:&lt;br&gt;
&lt;code&gt;Origin-Trial: TOKEN_GOES_HERE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;w-headline-link&quot; href=&quot;#feedback&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chrome wants to hear your thoughts and experiences using this API throughout the
Origin Trial process.&lt;/p&gt;
&lt;h3 id=&quot;feedback-design&quot;&gt;Feedback about the API design &lt;a class=&quot;w-headline-link&quot; href=&quot;#feedback-design&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Is there something about the API that&#39;s awkward or doesn&#39;t work as expected? Or
are there missing pieces that you need to implement your idea?&lt;/p&gt;
&lt;p&gt;File an issue on the &lt;a href=&quot;https://github.com/rayankans/content-index/issues&quot;&gt;Content Indexing API explainer GitHub
repo&lt;/a&gt;, or add your thoughts
to an existing issue.&lt;/p&gt;
&lt;h3 id=&quot;feedback-implementation&quot;&gt;Problem with the implementation? &lt;a class=&quot;w-headline-link&quot; href=&quot;#feedback-implementation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you find a bug with Chrome&#39;s implementation?&lt;/p&gt;
&lt;p&gt;File a bug at &lt;a href=&quot;https://new.crbug.com/&quot;&gt;https://new.crbug.com&lt;/a&gt;. Include as much
detail as you can, simple instructions for reproducing, and set &lt;strong&gt;Components&lt;/strong&gt;
to &lt;code&gt;Blink&amp;gt;ContentIndexing&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;planning-to-use&quot;&gt;Planning to use the API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#planning-to-use&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Planning to use the Content Indexing API in your web app? Your public support
helps Chrome prioritize features, and shows other browser vendors how critical it is
to support them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Be sure you have signed up for the &lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/2272066012008415233&quot;&gt;Origin
Trial&lt;/a&gt;
to show your interest and provide your domain and contact info.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Send a Tweet to &lt;a href=&quot;https://twitter.com/chromiumdev&quot;&gt;@ChromiumDev&lt;/a&gt; with
&lt;code&gt;#ContentIndexingAPI&lt;/code&gt; and details on where and how you&#39;re using it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;security-privacy&quot;&gt;What are some security and privacy implications of content indexing? &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-privacy&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://github.com/rayankans/content-index/blob/master/SECURITY_AND_PRIVACY.md&quot;&gt;the
answers&lt;/a&gt;
provided in response to the W3C&#39;s &lt;a href=&quot;https://www.w3.org/TR/security-privacy-questionnaire/&quot;&gt;Security and Privacy
questionnaire&lt;/a&gt;. If you
have further questions, please start a discussion via the project&#39;s &lt;a href=&quot;https://github.com/rayankans/content-index/issues&quot;&gt;GitHub
repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hero image by Maksym Kaharlytskyi on &lt;a href=&quot;https://unsplash.com/photos/Q9y3LRuuxmg&quot;&gt;Unsplash&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
<author>
<name>Jeff Posnick</name>
</author>
</entry>
<entry>
<title>Use web workers to run JavaScript off the browser&#39;s main thread</title>
<link href="https://web.dev/off-main-thread/"/>
<updated>2019-12-04T16:00:00-08:00</updated>
<id>https://web.dev/off-main-thread/</id>
<content type="html">&lt;p&gt;In the past 20 years,
the web has evolved dramatically from static documents with a few styles and images
to complex, dynamic applications.
However, one thing has remained largely unchanged:
we have just one thread per browser tab (with some exceptions)
to do the work of rendering our sites and running our JavaScript.&lt;/p&gt;
&lt;p&gt;As a result, the main thread has become incredibly overworked.
And as web apps grow in complexity,
the main thread becomes a significant bottleneck for performance.
To make matters worse,
the amount of time it takes to run code on the main thread for a given user
is &lt;strong&gt;almost completely unpredictable&lt;/strong&gt;
because device capabilities have a massive effect on performance.
That unpredictability will only grow as users access the web
from an increasingly diverse set of devices,
from hyper-constrained feature phones to high-powered,
high-refresh-rate flagship machines.&lt;/p&gt;
&lt;p&gt;If we want sophisticated web apps to reliably meet performance guidelines
like the &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/rail&quot;&gt;RAIL model&lt;/a&gt;—which
is based on empirical data about human perception and psychology—we
need ways to execute our code &lt;strong&gt;off the main thread (OMT)&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;If you want to hear more about the case for an OMT architecture,
watch my CDS 2019 talk below.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;w-youtube&quot;&gt;
&lt;iframe class=&quot;w-youtube__embed&quot; src=&quot;https://www.youtube.com/embed/7Rrv9qFMWNM&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;threading-with-web-workers&quot;&gt;Threading with web workers &lt;a class=&quot;w-headline-link&quot; href=&quot;#threading-with-web-workers&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Native platforms typically support parallel work
by allowing you to give a thread a function,
which runs in parallel with the rest of your program.
You can access the same variables from both threads,
and access to these shared resources can be synchronized
with mutexes and semaphores to prevent race conditions.&lt;/p&gt;
&lt;p&gt;In JavaScript, we can get roughly similar functionality from web workers,
which have been around since 2007
and supported across all major browsers since 2012.
Web workers run in parallel with the main thread,
but unlike native threading they can&#39;t share variables.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Don&#39;t confuse web workers with &lt;a href=&quot;https://web.dev/service-workers-cache-storage&quot;&gt;service workers&lt;/a&gt;
or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Worklet&quot;&gt;worklets&lt;/a&gt;.
While the names are similar, the functionality and uses are different.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To create a web worker, pass a file to the worker constructor,
which starts running that file in a separate thread:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./worker.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Communicate with the web worker by sending messages via the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage&quot;&gt;&lt;code&gt;postMessage&lt;/code&gt; API&lt;/a&gt;.
Pass the message value as a parameter in the &lt;code&gt;postMessage&lt;/code&gt; call
and then add a message event listener to the worker:&lt;/p&gt;
&lt;!--lint disable no-duplicate-headings-in-section--&gt;
&lt;h3 id=&quot;main.js&quot;&gt;&lt;code&gt;main.js&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#main.js&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./worker.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;worker.js&quot;&gt;&lt;code&gt;worker.js&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#worker.js&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Do stuff with the message&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;To send a message back to the main thread,
use the same &lt;code&gt;postMessage&lt;/code&gt; API in the web worker
and set up an event listener on the main thread:&lt;/p&gt;
&lt;h3 id=&quot;main.js-2&quot;&gt;&lt;code&gt;main.js&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#main.js-2&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./worker.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;worker.js-2&quot;&gt;&lt;code&gt;worker.js&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#worker.js-2&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Do stuff with the message&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Admittedly, this approach is somewhat limited.
Historically, web workers have mainly been used
for moving a single piece of heavy work off the main thread.
Trying to handle multiple operations with a single web worker gets unwieldy quickly:
you have to encode not only the parameters but also the operation in the message,
and you have to do bookkeeping to match responses to requests.
That complexity is likely why web workers haven&#39;t been adopted more widely.&lt;/p&gt;
&lt;p&gt;But if we could remove some of the difficulty of communicating
between the main thread and web workers,
this model could be a great fit for many use cases.
And, luckily, there&#39;s a library that does just that!&lt;/p&gt;
&lt;h2 id=&quot;comlink:-making-web-workers-less-work&quot;&gt;Comlink: making web workers less work &lt;a class=&quot;w-headline-link&quot; href=&quot;#comlink:-making-web-workers-less-work&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://npm.im/comlink&quot;&gt;Comlink&lt;/a&gt; is a library
whose goal is to let you use web workers
without having to think about the details of &lt;code&gt;postMessage&lt;/code&gt;.
Comlink lets you to share variables
between web workers and the main thread
almost like programming languages that natively support threading.&lt;/p&gt;
&lt;p&gt;You set up Comlink by importing it in a web worker
and defining a set of functions to expose to the main thread.
You then import Comlink on the main thread, wrap the worker,
and get access to the exposed functions:&lt;/p&gt;
&lt;h3 id=&quot;worker.js-3&quot;&gt;&lt;code&gt;worker.js&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#worker.js-3&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;expose&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;comlink&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; api &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;someMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* … */&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;expose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;api&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;main.js-3&quot;&gt;&lt;code&gt;main.js&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#main.js-3&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;wrap&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;comlink&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./worker.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; api &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;worker&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The &lt;code&gt;api&lt;/code&gt; variable on main thread behaves the same as the one in the web worker,
except that every function returns a promise for a value rather than the value itself.&lt;/p&gt;
&lt;h2 id=&quot;what-code-should-you-move-to-a-web-worker&quot;&gt;What code should you move to a web worker? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-code-should-you-move-to-a-web-worker&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Web workers don&#39;t have access to the DOM and many APIs
like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/USB&quot;&gt;WebUSB&lt;/a&gt;,
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API&quot;&gt;WebRTC&lt;/a&gt;, or
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API&quot;&gt;Web Audio&lt;/a&gt;,
so you can&#39;t put pieces of your app that rely on such access in a worker.
Still, every small piece of code moved to a worker buys more headroom
on the main thread for stuff that &lt;em&gt;has&lt;/em&gt; to be there—like updating the user interface.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Restricting UI access to the main thread is actually typical in other languages.
In fact, both iOS and Android call the main thread the &lt;em&gt;UI thread&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;One problem for web developers is that most web apps rely on a UI framework
like Vue or React to orchestrate everything in the app;
everything is a component of the framework and so is inherently tied to the DOM.
That would seem to make it difficult to migrate to an OMT architecture.&lt;/p&gt;
&lt;p&gt;However, if we shift to a model in which UI concerns are separated from other concerns,
like state management, web workers can be quite useful even with framework-based apps.
That&#39;s exactly the approach taken with PROXX.&lt;/p&gt;
&lt;h2 id=&quot;proxx:-an-omt-case-study&quot;&gt;PROXX: an OMT case study &lt;a class=&quot;w-headline-link&quot; href=&quot;#proxx:-an-omt-case-study&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Google Chrome team developed &lt;a href=&quot;https://web.dev/load-faster-like-proxx/&quot;&gt;PROXX&lt;/a&gt;
as a Minesweeper clone that meets
&lt;a href=&quot;https://developers.google.com/web/progressive-web-apps&quot;&gt;Progressive Web App&lt;/a&gt; requirements,
including working offline and having an engaging user experience.
Unfortunately, early versions of the game performed poorly on constrained devices
like feature phones, which led the team to realize that the main thread was a bottleneck.&lt;/p&gt;
&lt;p&gt;The team decided to use web workers to separate the game&#39;s visual state from its logic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The main thread handles rendering of animations and transitions.&lt;/li&gt;
&lt;li&gt;A web worker handles game logic, which is purely computational.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;This approach is similar to the Redux
&lt;a href=&quot;https://facebook.github.io/flux/&quot;&gt;Flux pattern&lt;/a&gt;,
so many Flux apps may be able to migrate fairly easily to an OMT architecture.
Take a look at &lt;a href=&quot;http://dassur.ma/things/react-redux-comlink/&quot;&gt;this blog post&lt;/a&gt;
to read more about applying OMT to a Redux app.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;OMT had interesting effects on PROXX&#39;s feature phone performance.
In the non-OMT version,
the UI is frozen for six seconds after the user interacts with it.
There&#39;s no feedback, and the user has to wait for the full six seconds
before being able to do something else.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot; style=&quot;max-width: 400px;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/off-main-thread/proxx-nonomt.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/off-main-thread/proxx-nonomt.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
UI response time in the &lt;strong&gt;non-OMT&lt;/strong&gt; version of PROXX.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In the OMT version, however, the game takes &lt;em&gt;twelve&lt;/em&gt; seconds to complete a UI update.
While that seems like a performance loss,
it actually leads to increased feedback to the user.
The slowdown occurs because the app is shipping more frames than the non-OMT version,
which isn&#39;t shipping any frames at all.
The user therefore knows that something is happening
and can continue playing as the UI updates,
making the game feel considerably better.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot; style=&quot;max-width: 400px;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/off-main-thread/proxx-omt.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/off-main-thread/proxx-omt.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
UI response time in the &lt;strong&gt;OMT&lt;/strong&gt; version of PROXX.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is a conscious tradeoff:
we give users of constrained devices an experience that &lt;em&gt;feels&lt;/em&gt; better
without penalizing users of high-end devices.&lt;/p&gt;
&lt;h2 id=&quot;implications-of-an-omt-architecture&quot;&gt;Implications of an OMT architecture &lt;a class=&quot;w-headline-link&quot; href=&quot;#implications-of-an-omt-architecture&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As the PROXX example shows,
OMT makes your app reliably run on a wider range of devices,
but it doesn&#39;t make your app faster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&#39;re just moving work from the main thread, not reducing the work.&lt;/li&gt;
&lt;li&gt;The extra communication overhead between the web worker
and the main thread can sometimes make things marginally slower.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;considering-the-tradeoffs&quot;&gt;Considering the tradeoffs &lt;a class=&quot;w-headline-link&quot; href=&quot;#considering-the-tradeoffs&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since the main thread is free to process user interactions
like scrolling while JavaScript is running,
there are fewer dropped frames even though total wait time may be marginally longer.
Making the user wait a bit is preferable to dropping a frame
because the margin of error is smaller for dropped frames:
dropping a frame happens in milliseconds,
while you have &lt;em&gt;hundreds&lt;/em&gt; of milliseconds before a user perceives wait time.&lt;/p&gt;
&lt;p&gt;Because of the unpredictability of performance across devices,
the goal of OMT architecture is really about &lt;strong&gt;reducing risk&lt;/strong&gt;—making
your app more robust in the face of highly variable runtime conditions—not
about the performance benefits of parallelization.
The increase in resilience and the improvements
to UX are more than worth any small tradeoff in speed.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Developers are sometimes concerned about the cost
of copying complex objects across the main thread and web workers.
There&#39;s more detail in the talk, but, in general,
you shouldn&#39;t break your performance budget
if your object&#39;s stringified JSON representation is less than 10 KB.
If you need to copy larger objects, consider using
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer&quot;&gt;ArrayBuffer&lt;/a&gt;
or &lt;a href=&quot;https://webassembly.org/&quot;&gt;WebAssembly&lt;/a&gt;.
You can read more about this issue in
&lt;a href=&quot;https://dassur.ma/things/is-postmessage-slow&quot;&gt;this blog post about &lt;code&gt;postMessage&lt;/code&gt; performance&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;a-note-about-tooling&quot;&gt;A note about tooling &lt;a class=&quot;w-headline-link&quot; href=&quot;#a-note-about-tooling&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Web workers aren&#39;t yet mainstream,
so most module tools—like &lt;a href=&quot;https://webpack.js.org/&quot;&gt;WebPack&lt;/a&gt;
and &lt;a href=&quot;https://github.com/rollup/rollup&quot;&gt;Rollup&lt;/a&gt;—don&#39;t support them out of the box.
(&lt;a href=&quot;https://parceljs.org/&quot;&gt;Parcel&lt;/a&gt; does though!)
Luckily, there are plugins to make web workers, well, &lt;em&gt;work&lt;/em&gt; with WebPack and Rollup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/worker-plugin&quot;&gt;worker-plugin&lt;/a&gt; for WebPack&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/surma/rollup-plugin-off-main-thread&quot;&gt;rollup-plugin-off-main-thread&lt;/a&gt; for Rollup&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summing-up&quot;&gt;Summing up &lt;a class=&quot;w-headline-link&quot; href=&quot;#summing-up&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make sure our apps are as reliable and accessible as possible, especially in an increasingly globalized marketplace, we need to support constrained devices—they&#39;re how most users are accessing the web globally. OMT offers a promising way to increase performance on such devices without adversely affecting users of high-end devices.&lt;/p&gt;
&lt;p&gt;Also, OMT has secondary benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It moves JavaScript execution costs to a separate thread.&lt;/li&gt;
&lt;li&gt;It moves &lt;em&gt;parsing&lt;/em&gt; costs, meaning UI might boot up faster.
That might reduce &lt;a href=&quot;https://web.dev/first-contentful-paint&quot;&gt;First Contentful Paint&lt;/a&gt;
or even &lt;a href=&quot;https://web.dev/interactive&quot;&gt;Time to Interactive&lt;/a&gt;,
which can in turn increase your
&lt;a href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; score.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Web workers don&#39;t have to be scary.
Tools like Comlink are taking the work out of workers
and making them a viable choice for a wide range of web applications.&lt;/p&gt;
</content>
<author>
<name>Surma </name>
</author>
</entry>
<entry>
<title>Next-generation web styling</title>
<link href="https://web.dev/next-gen-css-2019/"/>
<updated>2019-12-04T16:00:00-08:00</updated>
<id>https://web.dev/next-gen-css-2019/</id>
<content type="html">&lt;p&gt;There are a &lt;em&gt;ton&lt;/em&gt; of exciting things happening in CSS right now—and
many of them are already supported in today&#39;s browsers!
Our talk at CDS 2019, which you can watch below,
covers several new and upcoming features we thought should get some attention.&lt;/p&gt;
&lt;p&gt;This post focuses on the features you can use today,
so be sure to watch the talk
for a deeper discussion of upcoming features like Houdini.
You can also find demos for all the features we discuss on our
&lt;a href=&quot;https://web.dev/next-gen-css-2019/a.nerdy.dev/css-at-cds&quot;&gt;CSS@CDS page&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;w-youtube&quot;&gt;
&lt;iframe class=&quot;w-youtube__embed&quot; src=&quot;https://www.youtube.com/embed/-oyeaIirVC0&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;contents&quot;&gt;Contents &lt;a class=&quot;w-headline-link&quot; href=&quot;#contents&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#scroll-snap&quot;&gt;Scroll Snap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#:focus-within&quot;&gt;&lt;code&gt;:focus-within&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#media-queries-level-5&quot;&gt;Media Queries Level 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#logical-properties&quot;&gt;Logical properties&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#position:-sticky&quot;&gt;&lt;code&gt;position: sticky&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#backdrop-filter&quot;&gt;&lt;code&gt;backdrop-filter&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#is&quot;&gt;&lt;code&gt;:is()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#gap&quot;&gt;&lt;code&gt;gap&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#css-houdini&quot;&gt;CSS Houdini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#overflow&quot;&gt;Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;scroll-snap&quot;&gt;Scroll Snap &lt;a class=&quot;w-headline-link&quot; href=&quot;#scroll-snap&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap/Basic_concepts&quot;&gt;Scroll Snap&lt;/a&gt; lets you define snap points as the user scrolls your content vertically, horizontally, or both. It offers native scroll inertia and deceleration, and it&#39;s touch enabled.&lt;/p&gt;
&lt;p&gt;This sample code sets up horizontal scrolling in a &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; element with snap points aligned to the left sides of child &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; elements:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;section&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;overflow-x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;overscroll-behavior-x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; contain&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;scroll-snap-type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; x mandatory&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;section &gt; picture&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;scroll-snap-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; start&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Here&#39;s how it works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On the parent &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; element,
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;overflow-x&lt;/code&gt; is set to &lt;code&gt;auto&lt;/code&gt; to allow horizontal scrolling.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;overscroll-behavior-x&lt;/code&gt; is set to &lt;code&gt;contain&lt;/code&gt; to prevent any parent elements from scrolling when the user reaches the boundaries of the &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; element&#39;s scroll area. (This isn&#39;t strictly necessary for snapping, but it&#39;s usually a good idea.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scroll-snap-type&lt;/code&gt; is set to &lt;code&gt;x&lt;/code&gt;—for horizontal snapping—and &lt;code&gt;mandatory&lt;/code&gt;—to ensure that the viewport always snaps to the closest snap point.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;On the child &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; elements, &lt;code&gt;scroll-snap-align&lt;/code&gt; is set to start, which sets the snap points on the left side of each picture (assuming &lt;code&gt;direction&lt;/code&gt; is set to &lt;code&gt;ltr&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And here&#39;s a live demo:&lt;/p&gt;
&lt;iframe height=&quot;520&quot; style=&quot;display: block; width: 400px; max-width: 100%; margin: 0 auto;&quot; scrolling=&quot;no&quot; title=&quot;Awww Scroll Snap [horizontal]&quot; src=&quot;https://codepen.io/argyleink/embed/zYYZPqb?height=916&amp;theme-id=dark&amp;default-tab=result&quot; frameborder=&quot;no&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
See the Pen &lt;a href=&quot;https://codepen.io/argyleink/pen/zYYZPqb&quot;&gt;Awww Scroll Snap [horizontal]&lt;/a&gt; by Adam Argyle
(&lt;a href=&quot;https://codepen.io/argyleink&quot;&gt;@argyleink&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;p&gt;You can also take a look at demos for &lt;a href=&quot;https://codepen.io/argyleink/pen/oNNZoZj&quot;&gt;vertical scroll snap&lt;/a&gt; and &lt;a href=&quot;https://codepen.io/argyleink/pen/MWWpOmz&quot;&gt;matrix scroll snap&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;While scroll snap supports vertical snapping, be cautious when using it at the page level since it can feel like control is being taken from the user in some cases. It&#39;s usually best to apply snapping to a component on your page rather than the page itself.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;:focus-within&quot;&gt;&lt;code&gt;:focus-within&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#:focus-within&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within&quot;&gt;&lt;code&gt;:focus-within&lt;/code&gt;&lt;/a&gt; addresses a long-standing accessibility issue: there are many cases when focusing a child element should affect the presentation of a parent element so that the UI is accessible to users of assistive technologies.&lt;/p&gt;
&lt;p&gt;For example, if you have a dropdown menu with several items, the menu should remain visible while any of the items has focus. Otherwise, the menu disappears for keyboard users.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:focus-within&lt;/code&gt; tells the browser to apply a style when focus is on any child element of a specified element. Returning to the menu example, by setting &lt;code&gt;:focus-within&lt;/code&gt; on the menu element, you can make sure it stays visible when a menu item has focus:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.menu:focus-within&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;visibility&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; visible&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;&lt;img src=&quot;https://web.dev/next-gen-css-2019/menu-focus.png&quot; alt=&quot;An illustration showing the difference in behavior between focus and focus-within.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Try tabbing through the focusable elements in the demo below. You&#39;ll notice that the menus remain visible as you focus on the menu items:&lt;/p&gt;
&lt;iframe height=&quot;275&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;Simple CSS Dropdown Menu with Hover and :focus-within and Focus states&quot; src=&quot;https://codepen.io/una/embed/RMmogp?height=265&amp;theme-id=dark&amp;default-tab=result&quot; frameborder=&quot;no&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
See the Pen &lt;a href=&quot;https://codepen.io/una/pen/RMmogp&quot;&gt;Simple CSS Dropdown Menu with Hover and :focus-within and Focus states&lt;/a&gt; by Una Kravets
(&lt;a href=&quot;https://codepen.io/una&quot;&gt;@una&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h2 id=&quot;media-queries-level-5&quot;&gt;Media Queries Level 5 &lt;a class=&quot;w-headline-link&quot; href=&quot;#media-queries-level-5&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#environment-blending&quot;&gt;New media queries&lt;/a&gt; give us powerful ways to adjust the user experience of our apps based on a user&#39;s device preferences. Basically, the browser serves as a proxy for system-level preferences that we can respond to in our CSS using the &lt;code&gt;prefers-*&lt;/code&gt; group of media queries:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/next-gen-css-2019/prefers-media-queries.png&quot; alt=&quot;A diagram showing media queries interpreting system-level user preferences.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here are the new queries we think developers will be most excited about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2019/03/prefers-reduced-motion&quot;&gt;prefers-reduced-motion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/prefers-color-scheme/&quot;&gt;prefers-color-scheme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast&quot;&gt;prefers-contrast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-transparency&quot;&gt;prefers-reduced-transparency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors&quot;&gt;forced-colors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/inverted-colors&quot;&gt;inverted-colors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@media/light-level&quot;&gt;light-level&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These queries are a huge win for accessibility. Previously we had no way to know, for example, that a user had set their OS to high-contrast mode. If you wanted to provide a high-contrast mode for a web app that remained true to your brand, you had to ask users to choose it from UI within your app. Now you can detect the high-contrast setting from the OS using &lt;code&gt;prefers-contrast&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One exciting implication of these media queries is that we can design for multiple combinations of system-level user preferences to accommodate the wide range of user preferences and accessibility needs. If a user wants high-contrast dark mode when in dimly lit environments, you can do that!&lt;/p&gt;
&lt;p&gt;It&#39;s important to Adam that &amp;quot;prefers reduced motion&amp;quot; doesn&#39;t get implemented as &amp;quot;no motion.&amp;quot; The user is saying they prefer less motion, not that they don&#39;t want any animation. He asserts reduced motion is not no motion. Here&#39;s an example that uses a crossfade animation when the user prefers reduced motion:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/reduced-motion.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/reduced-motion.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;In Chrome Canary, you can test CSS that uses &lt;code&gt;prefers-reduced-motion&lt;/code&gt; or &lt;code&gt;prefers-color-scheme&lt;/code&gt; by choosing the appropriate settings in the DevTools &lt;strong&gt;Rendering&lt;/strong&gt; drawer. To access &lt;strong&gt;Rendering&lt;/strong&gt;, &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/command-menu&quot;&gt;open the Command Menu&lt;/a&gt; and run the &lt;code&gt;Show Rendering&lt;/code&gt; command.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;logical-properties&quot;&gt;Logical properties &lt;a class=&quot;w-headline-link&quot; href=&quot;#logical-properties&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties&quot;&gt;Logical properties&lt;/a&gt; solve a problem that has gained visibility as more developers tackle internationalization. Many layout properties like &lt;code&gt;margin&lt;/code&gt; and &lt;code&gt;padding&lt;/code&gt; assume a language that is read top-to-bottom and left-to-right.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/next-gen-css-2019/legacy-layout.png&quot; alt=&quot;A diagram showing traditional CSS layout properties.&quot;&gt;&lt;/p&gt;
&lt;p&gt;When designing pages for multiple languages with varying writing modes, developers have had to adjust all those properties individually across multiple elements, which quickly becomes a maintainability nightmare.&lt;/p&gt;
&lt;p&gt;Logical properties let you maintain layout integrity across translations and writing modes. They dynamically update based on the semantic ordering of content rather than its spatial arrangement. With logical properties, each element has two dimensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;block&lt;/strong&gt; dimension is &lt;strong&gt;perpendicular&lt;/strong&gt; to the flow of text in a line. (In English, &lt;code&gt;block-size&lt;/code&gt; is the same as &lt;code&gt;height&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;inline&lt;/strong&gt; dimension is &lt;strong&gt;parallel&lt;/strong&gt; to the flow of text in a line. (In English, &lt;code&gt;inline-size&lt;/code&gt; is the same as &lt;code&gt;width&lt;/code&gt;.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These dimension names apply to all logical layout properties. So, for example, in English, &lt;code&gt;block-start&lt;/code&gt; is the same as &lt;code&gt;top&lt;/code&gt;, and &lt;code&gt;inline-end&lt;/code&gt; is the same as &lt;code&gt;right&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/next-gen-css-2019/logical-layout.png&quot; alt=&quot;A diagram showing new CSS logical layout properties.&quot;&gt;&lt;/p&gt;
&lt;p&gt;With logical properties, you can automatically update your layout for other languages by simply changing the &lt;code&gt;writing-mode&lt;/code&gt; and &lt;code&gt;direction&lt;/code&gt; properties for your page rather than updating dozens of layout properties on individual elements.&lt;/p&gt;
&lt;p&gt;You can see how logical properties work in the demo below by setting the &lt;code&gt;writing-mode&lt;/code&gt; property on the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element to different values:&lt;/p&gt;
&lt;iframe height=&quot;750&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;Logical Properties Demo&quot; src=&quot;https://codepen.io/una/embed/mddxpaY?height=265&amp;theme-id=dark&amp;default-tab=css,result&quot; frameborder=&quot;no&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
See the Pen &lt;a href=&quot;https://codepen.io/una/pen/mddxpaY&quot;&gt;Logical Properties Demo&lt;/a&gt; by Una Kravets
(&lt;a href=&quot;https://codepen.io/una&quot;&gt;@una&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h2 id=&quot;position:-sticky&quot;&gt;&lt;code&gt;position: sticky&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#position:-sticky&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An element with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/position#Sticky_positioning&quot;&gt;&lt;code&gt;position: sticky&lt;/code&gt;&lt;/a&gt; remains in block flow until it starts to go offscreen,
at which point it stops scrolling with the rest of the page
and sticks to the position specified by the element&#39;s &lt;code&gt;top&lt;/code&gt; value.
The space allocated for that element remains in the flow,
and the element returns to it when the user scrolls back up.&lt;/p&gt;
&lt;p&gt;Sticky positioning lets you create many useful effects that previously required JavaScript. To show some of the possibilities, we&#39;ve created several demos. Each demo uses largely the same CSS and only slightly adjusts the HTML markup to create each effect.&lt;/p&gt;
&lt;h3 id=&quot;sticky-stack&quot;&gt;&lt;a href=&quot;https://codepen.io/argyleink/pen/YzzZyMx&quot;&gt;Sticky Stack&lt;/a&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#sticky-stack&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this demo, all sticky elements share the same container. That means that each sticky element slides over the previous one as the user scrolls down. The sticky elements share the same stuck position.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/sticky-stack.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/sticky-stack.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;sticky-slide&quot;&gt;&lt;a href=&quot;https://codepen.io/argyleink/pen/abbJOjP&quot;&gt;Sticky Slide&lt;/a&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#sticky-slide&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here, the sticky elements are cousins. (That is, their parents are siblings.) When a sticky element hits the lower boundary of its container, it moves up with the container, creating the impression that lower sticky elements are pushing up higher ones. In other words, they appear to compete for the stuck position.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/sticky-slide.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/sticky-slide.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;sticky-desperado&quot;&gt;&lt;a href=&quot;https://codepen.io/argyleink/pen/qBBrbyx&quot;&gt;Sticky Desperado&lt;/a&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#sticky-desperado&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like Sticky Slide, the sticky elements in this demo are cousins. However, they&#39;ve been placed in containers set to a two-column grid layout.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/sticky-desperado.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/sticky-desperado.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;backdrop-filter&quot;&gt;&lt;code&gt;backdrop-filter&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#backdrop-filter&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter&quot;&gt;&lt;code&gt;backdrop-filter&lt;/code&gt;&lt;/a&gt; property lets you apply graphical effects to the area &lt;em&gt;behind&lt;/em&gt; an element rather than to the element itself. This makes lots of cool effects that were previously only achievable using complicated CSS and JavaScript hacks doable with one line of CSS.&lt;/p&gt;
&lt;p&gt;For example, this demo uses &lt;code&gt;backdrop-filter&lt;/code&gt; to achieve native OS-style blurring:&lt;/p&gt;
&lt;iframe height=&quot;510&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;mddjjor&quot; src=&quot;https://codepen.io/una/embed/mddjjor?height=265&amp;theme-id=dark&amp;default-tab=result&quot; frameborder=&quot;no&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
See the Pen &lt;a href=&quot;https://codepen.io/una/pen/mddjjor&quot;&gt;mddjjor&lt;/a&gt; by Una Kravets
(&lt;a href=&quot;https://codepen.io/una&quot;&gt;@una&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;p&gt;We already have a &lt;a href=&quot;https://web.dev/backdrop-filter/&quot;&gt;great post about &lt;code&gt;backdrop-filter&lt;/code&gt;&lt;/a&gt;, so head there for more info.&lt;/p&gt;
&lt;h2 id=&quot;:is()&quot;&gt;&lt;code&gt;:is()&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#:is()&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:is&quot;&gt;&lt;code&gt;:is()&lt;/code&gt; pseudo-class&lt;/a&gt; is actually over ten years old, it still doesn&#39;t see as much use as we think it deserves. It takes a comma-separated list of selectors as its argument and matches any selectors in that list. That flexibility makes it incredibly handy and can significantly reduce the amount of CSS you ship.&lt;/p&gt;
&lt;p&gt;Here&#39;s a quick example:&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/is-animation.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/next-gen-css-2019/is-animation.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button.focus,&lt;br&gt;button:focus&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; …&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token selector&quot;&gt;article &gt; h1,&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;article &gt; h2,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;article &gt; h3,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;article &gt; h4,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;article &gt; h5,&lt;/span&gt;&lt;br&gt;article &gt; h6&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; …&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* selects the same elements as the code above */&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button:is(.focus, :focus)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; …&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;article &gt; :is(h1,h2,h3,h4,h5,h6)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; …&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;gap&quot;&gt;&lt;code&gt;gap&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#gap&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout&quot;&gt;CSS grid layout&lt;/a&gt; has had &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/gap&quot;&gt;&lt;code&gt;gap&lt;/code&gt;&lt;/a&gt; (previously &lt;code&gt;grid-gap&lt;/code&gt;) for some time. By specifying the internal spacing of a containing element rather than the spacing around child elements, &lt;code&gt;gap&lt;/code&gt; solves many common layout issues. For example, with gap, you don&#39;t have to worry about margins on child elements causing unwanted whitespace around the edges of a containing element:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/next-gen-css-2019/gap.png&quot; alt=&quot;Illustration showing how the gap property avoids unintended spacing around edges of a container element.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Even better news: &lt;code&gt;gap&lt;/code&gt; is coming to flexbox, bringing all the same spacing perks that grid has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There&#39;s one spacing declaration rather than many.&lt;/li&gt;
&lt;li&gt;There&#39;s no need to establish conventions for your project about which child elements should own spacing—the containing element owns the spacing instead.&lt;/li&gt;
&lt;li&gt;The code is more easily understandable than older strategies like the &lt;a href=&quot;https://alistapart.com/article/axiomatic-css-and-lobotomized-owls/&quot;&gt;lobotomized owl&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following video shows the benefits of using a single &lt;code&gt;gap&lt;/code&gt; property for two elements, one with a grid layout and one with a flex layout:&lt;/p&gt;
&lt;p&gt;Right now, only FireFox supports &lt;code&gt;gap&lt;/code&gt; in flex layouts, but play around with this demo to see how it works:&lt;/p&gt;
&lt;iframe height=&quot;600&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;Gappy&quot; src=&quot;https://codepen.io/argyleink/embed/abbVqEv?height=265&amp;theme-id=dark&amp;default-tab=css,result&quot; frameborder=&quot;no&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
See the Pen &lt;a href=&quot;https://codepen.io/argyleink/pen/abbVqEv&quot;&gt;Gappy&lt;/a&gt; by Adam Argyle
(&lt;a href=&quot;https://codepen.io/argyleink&quot;&gt;@argyleink&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h2 id=&quot;css-houdini&quot;&gt;CSS Houdini &lt;a class=&quot;w-headline-link&quot; href=&quot;#css-houdini&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Houdini&quot;&gt;Houdini&lt;/a&gt; is a set of low-level APIs for the browser&#39;s rendering engine that lets you tell the browser how to interpret custom CSS. In other words, it gives you access to the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model&quot;&gt;CSS Object Model&lt;/a&gt;, letting you &lt;em&gt;extend&lt;/em&gt; CSS via Javascript. This has several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It gives you much more power to create custom CSS features.&lt;/li&gt;
&lt;li&gt;It&#39;s easier to separate rendering concerns from application logic.&lt;/li&gt;
&lt;li&gt;It&#39;s more performant than the CSS polyfilling we currently do with JavaScript since the browser will no longer have to parse scripts and do a second rendering cycle; Houdini code is parsed in the first rendering cycle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/next-gen-css-2019/houdini.png&quot; alt=&quot;Illustration showing how Houdini works compared to traditional JavaScript polyfills.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Houdini is an umbrella name for &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Houdini#The_Houdini_APIs&quot;&gt;several APIs&lt;/a&gt;. If you want more information about them and their current status, take a look at &lt;a href=&quot;https://ishoudinireadyyet.com/&quot;&gt;Is Houdini Ready Yet?&lt;/a&gt; In our talk, we covered the Properties and Values API, the Paint API, and the Animation Worklet because they&#39;re currently the most supported. We could easily dedicate a full post to each of these exciting APIs, but, for now, check out our talk for an overview and some cool demos that start to give a sense of what you can do with the APIs.&lt;/p&gt;
&lt;h2 id=&quot;overflow&quot;&gt;Overflow &lt;a class=&quot;w-headline-link&quot; href=&quot;#overflow&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a few more things on the horizon that we wanted to discuss but didn&#39;t have time to cover in depth, so we ran through them in a speed round.⚡ If you haven&#39;t heard of some of these features yet, be sure to watch &lt;a href=&quot;https://youtu.be/-oyeaIirVC0?t=1825&quot;&gt;the last part of the talk&lt;/a&gt;!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;: a property that will allow you to set height and width at the same time&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aspect-ratio&lt;/code&gt;: a property that sets an aspect ratio for elements that don&#39;t have one intrinsically&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min()&lt;/code&gt;, &lt;code&gt;max()&lt;/code&gt;, and &lt;code&gt;clamp()&lt;/code&gt;: functions that will let you set numeric constraints on any CSS property, not just width and height&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list-style-type&lt;/code&gt; an existing property, but it will soon support a wider range of values, including emoji and SVGs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;display: outer inner&lt;/code&gt;: The &lt;code&gt;display&lt;/code&gt; property will soon accept two parameters, which will let you explicitly specify its outer and inner layouts rather than using compound keywords like &lt;code&gt;inline-flex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CSS regions: will let you fill a specified, non-rectangular area that content can flow into and out of&lt;/li&gt;
&lt;li&gt;CSS modules: JavaScript will be able to request a CSS module and get a rich object back that&#39;s easy to perform operations on&lt;/li&gt;
&lt;/ul&gt;
</content>
<author>
<name>Adam Argyle</name>
</author><author>
<name>Una Kravets</name>
</author>
</entry>
<entry>
<title>Feedback from the summer 2019 image optimization survey</title>
<link href="https://web.dev/image-optimization-survey-2019/"/>
<updated>2019-11-21T16:00:00-08:00</updated>
<id>https://web.dev/image-optimization-survey-2019/</id>
<content type="html">&lt;p&gt;This post lists the freeform feedback that Google Web DevRel received in its Summer 2019
image optimization techniques survey.
Responses were solicited through &lt;a href=&quot;https://developers.google.com/web&quot;&gt;Web Fundamentals&lt;/a&gt; and
&lt;a href=&quot;https://twitter.com/chromiumdev&quot;&gt;@ChromiumDev&lt;/a&gt;. The motivation for the survey was to find out why
most sites don&#39;t follow image optimization best practices even though they seem like a relatively
easy way to improve performance. The response data isn&#39;t listed here because there were flaws
in the survey methodology.&lt;/p&gt;
&lt;h2 id=&quot;audience&quot;&gt;Audience &lt;a class=&quot;w-headline-link&quot; href=&quot;#audience&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If you&#39;re a web developer, you might find this post useful for discovering new image optimization
techniques, or details on how other web developers have solved a problem that you&#39;re facing, as
well as the costs, benefits, and limitations of each technique.&lt;/li&gt;
&lt;li&gt;If you&#39;re an image service or image CDN provider, this post might help you find new market
opportunities.&lt;/li&gt;
&lt;li&gt;If you&#39;re a framework, build tool, or CMS developer, this post might give you ideas on new
features to implement.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;comments&quot;&gt;Comments &lt;a class=&quot;w-headline-link&quot; href=&quot;#comments&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;webp&quot;&gt;WebP &lt;a class=&quot;w-headline-link&quot; href=&quot;#webp&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Why&#39;s this important?
Image formats such as &lt;a href=&quot;https://web.dev/serve-images-webp&quot;&gt;WebP&lt;/a&gt; can result in smaller files and better quality than older formats such as JPEG and PNG.
There are several techniques for using modern formats with fallback for older browsers.&lt;/p&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;I do like WebP but it isn&#39;t yet fully ready. Moreover, our WordPress doesn&#39;t support WebP. One of the most popular photo editing apps, Photoshop, also doesn&#39;t support WebP out of the box. So we can&#39;t rely on 3rd party apps or services for image compression.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Make WebP usable on Safari.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;I would love to use WebP if I could export them from Photoshop/Figma/Sketch and all browsers supported it.&amp;quot; [Note: Sketch does support WebP]&lt;/li&gt;
&lt;li&gt;&amp;quot;Next gen formatting solution would be great.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Stop pushing WebP so hard when browser support is poor, and consider the need for PNG instead of JPEG for screenshots.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Google Docs doesn&#39;t support WebP.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We would use WebP exclusively, but are concerned about browser compatibility.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;First fix browser compatibility and update legacy browsers or add legacy fixes, then people will be more inclined to adopt to new image types like WebP…&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Encourage plugin/theme developers to consider providing support to WebP and other next-gen image types, so that non-developers don&#39;t need to fiddle with it.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;svg-and-vector-images&quot;&gt;SVG and vector images &lt;a class=&quot;w-headline-link&quot; href=&quot;#svg-and-vector-images&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;If possible I&#39;m using (animated) SVG. gatsby-image fixed a lot of this. But when you dig into what they&#39;ve done, it&#39;s completely unrealistic that a normal website should have to build out something like that to get images to work right. The browser should take on more of this responsibility.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Would it be possible to document how to create SVG animations with &lt;a href=&quot;https://airbnb.io/lottie/#/&quot;&gt;lottie.js&lt;/a&gt;?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We try to use big resolution JPEG pictures with low sizes in our website most of the time to avoid loading times. We also ensure to use SVGs when necessary to provide quality for responsive design.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We try to use optimized vector graphics for all but pics if possible.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;other-image-formats&quot;&gt;Other image formats &lt;a class=&quot;w-headline-link&quot; href=&quot;#other-image-formats&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;We [need to] better educate people to stop using GIF.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;lazy-loading&quot;&gt;Lazy loading &lt;a class=&quot;w-headline-link&quot; href=&quot;#lazy-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Why&#39;s this important?
&lt;a href=&quot;https://web.dev/use-lazysizes-to-lazyload-images&quot;&gt;Requesting image files just in time&lt;/a&gt;, rather than getting all
the images for a page as soon as it loads, can improve performance and reduce data cost.&lt;/p&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Please keep the user in mind when considering features such as lazy load, because for many it&#39;s annoying.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Make the lazy load attribute work with background-image please.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We love that lazy loading of images is finally becoming a native feature.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Frameworks should do better asset processing out of the box.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We have converted from lazy loading a long time ago. User reports of millions of images and sites &amp;quot;NOT LOADING&amp;quot;. That was understanding our team summarized it as. It&#39;s hard for a non-technical users to describe issues.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;I&#39;m keen to get a better understanding of using Intersection Observer API for lazy loading rather than using traditional techniques.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;This well works for me: &lt;a href=&quot;https://pwafire.org/developer/codelabs/progressive-loading&quot;&gt;pwafire.org/developer/codelabs/progressive-loading&lt;/a&gt;.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;background-images&quot;&gt;Background images &lt;a class=&quot;w-headline-link&quot; href=&quot;#background-images&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;I usually load images as backgrounds in CSS.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;The &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag is problematic and difficult to control fine-grained details about, especially with user-submitted content. We use &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and background-image styling much more often as it allows us to use background-size, background-position, and prevent right-click saving of the image.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;transparency&quot;&gt;Transparency &lt;a class=&quot;w-headline-link&quot; href=&quot;#transparency&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;It&#39;s 2019. How are JPGs still without alpha transparency?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;I only really use PNGs for photographs when I need a transparent background.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;low-quality-image-placeholders-(lqips)&quot;&gt;Low Quality Image Placeholders (LQIPs) &lt;a class=&quot;w-headline-link&quot; href=&quot;#low-quality-image-placeholders-(lqips)&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;We use LQIPS and it&#39;s a great technique to keep visitors engaged without loading high quality images really early.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;performance&quot;&gt;Performance &lt;a class=&quot;w-headline-link&quot; href=&quot;#performance&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;We actually had a recent performance issue with images. As a user scrolls down on our site, we show the next 60 cards which include a thumbnail. Due to the 6 connection limit on the same domain, the thumbnails were being blocked as well as the next AJAX request to get the next 60 cards if a user continues to scroll down.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We would love to use HTTP/2 but most of our customers use IE11! We are therefore exploring domain sharding / loading AJAX JSON data requests off a different domain.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;sizing&quot;&gt;Sizing &lt;a class=&quot;w-headline-link&quot; href=&quot;#sizing&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Why&#39;s this important?
The &lt;a href=&quot;https://web.dev/use-srcset-to-automatically-choose-the-right-image&quot;&gt;&lt;code&gt;srcset&lt;/code&gt;&lt;/a&gt; attribute provides alternative image sources. You specify width or pixel density so the browser can choose the smallest image without needing to download images to calculate dimensions.&lt;/p&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Sorry for intrinsicsize; leveraging height/width seems better to me.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Looking for a way to generate less sizes, right now it&#39;s ~12.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Dynamic resizing of images is really hard and impossible without JS.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;A tool like &lt;a href=&quot;http://responsivebreakpoints.com/&quot;&gt;responsivebreakpoints.com&lt;/a&gt; is good for web.dev :).&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;high-quality-and-high-resolution-images&quot;&gt;High quality and high-resolution images &lt;a class=&quot;w-headline-link&quot; href=&quot;#high-quality-and-high-resolution-images&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;How to download compress images without losing DPI quality?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We&#39;re a document management company. Our apps handle MILLIONS of hi-res scanned images, usually TIFF or PDF.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;It&#39;s a hassle. Hi-res img files are necessary for print format; must be optimized for web. It&#39;s a hassle to downsize images for web but it&#39;s a show-stopper if authors only supply lightweight files for images destined for print publication. We wind up giving mixed messages about requirements for submission of manuscripts with artwork. We then wind up with complex workflows for processing those materials.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;browser-capability&quot;&gt;Browser capability &lt;a class=&quot;w-headline-link&quot; href=&quot;#browser-capability&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Auto responsive src crop from browser as native feature would be very useful as it is time consuming to crop all images to 4 sizes and writing all the markup. If we can upload one large photo and writing a simple picture tag that browsers will automatically create the multiple src attributes that would be a winning feature.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Personally I&#39;m having a hard time avoiding page reflows when image with is set by CSS for responsive images (max-width: 100%; height auto or height: width: 100%; height auto), especially in combination with art direction from adaptive images/picture tag. Best way to avoid seems to use the &amp;quot;negative padding hack&amp;quot; for a fixed image ratio and then position the image inside this ratio box. Better browser support/responsive image handling would be a really great help!&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Please disable GIF &amp;quot;autoplay&amp;quot; by fetching just the first frame.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cdns-and-image-services&quot;&gt;CDNs and image services &lt;a class=&quot;w-headline-link&quot; href=&quot;#cdns-and-image-services&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Why&#39;s this important?
&lt;strong&gt;Image services&lt;/strong&gt; make it easier to optimize your images. You usually just upload one high-resolution
version of each image and then use a web service API to optimize or transform the image as needed.
&lt;strong&gt;Content Delivery Networks (CDNs)&lt;/strong&gt; optimize the distribution and delivery of images and other website assets
and sometimes provide optimization services, such as automatically delivering WebP images to supporting browsers instead of PNG or JPEG, without changing the file extension.&lt;/p&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Google should provide a free CDN like Cloudflare.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Maybe more tooling to set up dynamic scaling and CDNs with different providers would be nice.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;A single oversized overcompressed image is a very decent solution with no extra production cost. You need around 1000 pixels wide images for mobile (500px render width) and that is also the size you need for large/desktop non-retina displays. I think images resize CDNs are a very bad solution, although I have used it in the past. The CMS should handle the resizing and of that is too complex to set up, a single solution is a good compromise (for now).&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;CloudFlare auto-scales our images to best match the user&#39;s display. So we can save on loading time because images are loaded in relative to the user&#39;s display. For example, if a user is on a phone, it won&#39;t load in a desktop-sized background.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Cloudflare does this in the background without us having to do anything except check a box in our settings panel.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Just to reiterate, the only reason I can successfully use srcset, etc. is due to the ease of Cloudinary. But Cloudinary gets expensive, &lt;em&gt;really&lt;/em&gt; fast. this feels like a major hole in the development experience.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;We need a way to easily auto crop images in a smart way so they can work with different aspect ratios in different contexts.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;I also use images from Other providers like Unsplash where there is very less control of resolution, quality and compression.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cms-platform-and-framework&quot;&gt;CMS, platform, and framework &lt;a class=&quot;w-headline-link&quot; href=&quot;#cms-platform-and-framework&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;I still struggle to find out what is the best way to use images, when I am building a site using a CMS. Authors tend to configure images with different dimensions and expect images not to shrink or scale. I am not sure if it is ok to set max-width or max-height on images&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Been using gatsby-image for the last few projects and have never looked back.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Images are often the hard part as they are put into CMS by end user, they may use any size, format, sometimes original image in ideal image format and dimensions are not available.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Images are difficult to maintain since our system is self-serve adding controls is difficult unless things happen automatically without affecting resolution. Also for us images don&#39;t look correct in mobile vs desktop&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;I help people to optimize their sites (WordPress). The biggest problems I&#39;ve seen for images are: Need to depend on a CDN or plugins to create WebP. srcset/picture has to be coded properly by theme developers. Most of the lazy loading plugins loads slowly giving bad UX. Background images are hard to lazy load.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;costbenefit&quot;&gt;Cost/benefit &lt;a class=&quot;w-headline-link&quot; href=&quot;#costbenefit&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;The new practices are effective but increase the development time of the sites.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;The lack of adherence to the new standards such as srcset and WebP has been slow to be adopted by many Fortune 500 companies. Seeing this, many companies have resisted the change as an unnecessary development cost for current websites. The performance gains are not widely discussed or reported by the end user (UX). If anything, it makes it somewhat harder to save images from the web.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Costly to create and manage multiple sizes, versions.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;They take up a lot of space on our server.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;seo&quot;&gt;SEO &lt;a class=&quot;w-headline-link&quot; href=&quot;#seo&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;It&#39;s difficult to balance between acceptable image quality and file size. On one hand, I want fast loading for the SEO benefit, but on the other hand, poor quality images will detract from the UI/UX.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-role-of-images-on-the-web&quot;&gt;The role of images on the web &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-role-of-images-on-the-web&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;There are too many on the web. Stop using useless images that don&#39;t enhance the written content.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Do you still remember the time when the web didn&#39;t have images and we shared selfies as ASCII-art?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tooling-guidance-standards-and-best-practice:-frustrations-and-requests&quot;&gt;Tooling, guidance, standards and best practice: frustrations and requests &lt;a class=&quot;w-headline-link&quot; href=&quot;#tooling-guidance-standards-and-best-practice:-frustrations-and-requests&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[One participant wrote a &lt;a href=&quot;https://stokito.wordpress.com/2019/07/19/how-to-make-web-images-better/&quot;&gt;blog post&lt;/a&gt; in response to this survey]&lt;/li&gt;
&lt;li&gt;&amp;quot;The requirements seems to constantly change. As a web developer it is extremely frustrating because it is time consuming to save out the images in the first place. We optimize the best we can, we check the site and then months later Google has decided that the images could be even more compressed or need to be in a different format. This prevents us from providing the best possible solution to our client that lasts and instead creates a costly endeavor for them and us. Some of our small business clients simply don&#39;t have the budget for us to keep fixing images and re-saving them out to adhere to the requirements. We don&#39;t have the budget to do this work within their management packages. Writing the code to call different image sizes for different devices is also time consuming. It would be great to come up with a system of saving out images that would be consistent for a longer period of time.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Yes, I think you got &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/audits/budgets&quot;&gt;Keep Request Counts Low And File Sizes Small&lt;/a&gt; all wrong in Lighthouse. If a site serves over HTTP1.x then sure, but if a site serves over HTTP2 then the number of requests is less important or not even an issue if originating from the same hostname. I have a lite website, but I load 30 small WebP files of approx 35 requests total, over HTTP2 on the same hostname. Lighthouse is flagging this as an &amp;quot;Keep Request Counts Low And File Sizes Small&amp;quot; issue whereas it is superfast and because of the HTTP2 on the same hostname the number of requests are not a problem. And yes, the files are already small (most between 1 KB and 2 KB or less). I could load a sprite but then more CSS computing needs to be done. So please update the &amp;quot;Keep Request Counts Low And File Sizes Small&amp;quot; report in Lighthouse to take HTTP2 over same hostname into account.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;It has been a struggle for people to remember to compress their images.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Cross browser behavior remains unpredictable so the simplest solutions are often the safest.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
</content>
<author>
<name>Sam Dutton</name>
</author>
</entry>
<entry>
<title>Get started with Web Bundles</title>
<link href="https://web.dev/web-bundles/"/>
<updated>2019-11-10T16:00:00-08:00</updated>
<id>https://web.dev/web-bundles/</id>
<content type="html">&lt;p&gt;Bundling a full website as a single file and making it shareable
opens up new use cases for the web. Imagine a world where you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create your own content and distribute it in all sorts of ways without being
restricted to the network&lt;/li&gt;
&lt;li&gt;Share a web app or piece of web content with your friends via Bluetooth or Wi-Fi Direct&lt;/li&gt;
&lt;li&gt;Carry your site on your own USB or even host it on your own local network&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Web Bundles API is a bleeding edge proposal that lets you do all of this.&lt;/p&gt;
&lt;h2 id=&quot;introducing-the-web-bundles-api&quot;&gt;Introducing the Web Bundles API &lt;a class=&quot;w-headline-link&quot; href=&quot;#introducing-the-web-bundles-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A Web Bundle is a file format for encapsulating one or more HTTP resources in a
single file. It can include one or more HTML files, JavaScript files,
images, or stylesheets.&lt;/p&gt;
&lt;p&gt;Web Bundles, more formally known as &lt;a href=&quot;https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html&quot;&gt;Bundled HTTP
Exchanges&lt;/a&gt;,
are part of the &lt;a href=&quot;https://goto.google.com/webpackaging-one-pager&quot;&gt;Web Packaging&lt;/a&gt;
proposal.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/web-bundles/webbundle.png&quot; alt=&quot;A figure demonstrating that a Web Bundle is a collection of web resources.&quot; style=&quot;max-width: 75%&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
How Web Bundles work
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;HTTP resources in a Web Bundle are indexed by request URLs, and can optionally
come with signatures that vouch for the resources. Signatures allow browsers to
understand and verify where each resource came from, and treats each as coming
from its true origin. This is similar to how &lt;a href=&quot;https://developers.google.com/web/updates/2018/11/signed-exchanges&quot;&gt;Signed HTTP Exchanges&lt;/a&gt;,
a feature for signing a single HTTP resource, are handled.&lt;/p&gt;
&lt;p&gt;This article walks you through what a Web Bundle is and how to use one.&lt;/p&gt;
&lt;h2 id=&quot;explaining-web-bundles&quot;&gt;Explaining Web Bundles &lt;a class=&quot;w-headline-link&quot; href=&quot;#explaining-web-bundles&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To be precise, a Web Bundle is a &lt;a href=&quot;https://cbor.io/&quot;&gt;CBOR file&lt;/a&gt; with a &lt;code&gt;.wbn&lt;/code&gt; extension (by convention) which
packages HTTP resources into a binary format, and is served with the &lt;code&gt;application/webbundle&lt;/code&gt; MIME
type. You can read more about this in the &lt;a href=&quot;https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#top-level&quot;&gt;Top-level structure&lt;/a&gt;
section of the spec draft.&lt;/p&gt;
&lt;p&gt;Web Bundles have multiple unique features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Encapsulates multiple pages, enabling bundling of a complete website into a single file&lt;/li&gt;
&lt;li&gt;Enables executable JavaScript, unlike MHTML&lt;/li&gt;
&lt;li&gt;Uses &lt;a href=&quot;https://tools.ietf.org/id/draft-ietf-httpbis-variants-00.html&quot;&gt;HTTP Variants&lt;/a&gt; to do
content negotiation, which enables internationalization with the &lt;code&gt;Accept-Language&lt;/code&gt;
header even if the bundle is used offline&lt;/li&gt;
&lt;li&gt;Loads in the context of its origin when cryptographically signed by its publisher&lt;/li&gt;
&lt;li&gt;Loads nearly instantly when served locally&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These features open multiple scenarios. One common scenario is the ability to
build a self-contained web app that&#39;s easy to share and usable without an
internet connection. For example, say you&#39;re on an airplane from Tokyo to San Francisco with
your friend. You don&#39;t like the in-flight entertainment. Your friend is playing an interesting
web game called &lt;a href=&quot;https://proxx.app/&quot;&gt;PROXX&lt;/a&gt;, and tells you that she downloaded the game as a Web
Bundle before boarding the plane. It works flawlessly offline. Before Web
Bundles, the story would end there and you would either have to take turns
playing the game on your friend&#39;s device, or find something else to pass the
time. But with Web Bundles, here&#39;s what you can now do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ask your friend to share the &lt;code&gt;.wbn&lt;/code&gt; file of the game. For example the file
could easily be shared peer-to-peer using a file sharing app.&lt;/li&gt;
&lt;li&gt;Open the &lt;code&gt;.wbn&lt;/code&gt; file in a browser that supports Web Bundles.&lt;/li&gt;
&lt;li&gt;Start playing the game on your own device and try to beat your friend&#39;s high
score.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&#39;s a video that explains this scenario.&lt;/p&gt;
&lt;div class=&quot;w-youtube&quot;&gt;
&lt;iframe class=&quot;w-youtube__embed&quot; src=&quot;https://www.youtube.com/embed/xAujz66la3Y&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;As you can see, a Web Bundle can contain every resource, making it work offline
and load instantly.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Currently Chrome 80 only supports unsigned bundles (that is, Web Bundles without
origin signatures). Bundling PROXX without signatures doesn&#39;t work
well due to web worker cross-origin issues. Chrome is working on fixing this. In
the meantime, check out &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/heads/master/content/browser/web_package/using_web_bundles.md#Dealing-with-Common-Problems-in-Unsigned-Bundles&quot;&gt;Dealing with Common Problems in Unsigned
Bundles&lt;/a&gt;
to learn how to avoid cross-origin issues.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;building-web-bundles&quot;&gt;Building Web Bundles &lt;a class=&quot;w-headline-link&quot; href=&quot;#building-web-bundles&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/WICG/webpackage/tree/master/go/bundle&quot;&gt;&lt;code&gt;go/bundle&lt;/code&gt;&lt;/a&gt; CLI is currently the
easiest way to bundle a website. &lt;code&gt;go/bundle&lt;/code&gt; is a reference implementation of the Web Bundles
specification built in &lt;a href=&quot;https://golang.org/&quot;&gt;Go&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://golang.org/doc/install&quot;&gt;Install Go&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;code&gt;go/bundle&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;go get -u github.com/WICG/webpackage/go/bundle/cmd/&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone the &lt;a href=&quot;https://github.com/developit/preact-todomvc&quot;&gt;preact-todomvc&lt;/a&gt; repository and build
the web app to get ready to bundle the resources.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone https://github.com/developit/preact-todomvc.git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cd&lt;/span&gt; preact-todomvc&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; i&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run build&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the &lt;code&gt;gen-bundle&lt;/code&gt; command to build a &lt;code&gt;.wbn&lt;/code&gt; file.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;gen-bundle -dir build -baseURL https://preact-todom.vc/ -primaryURL https://preact-todom.vc/ -o todomvc.wbn&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Congratulations! TodoMVC is now a Web Bundle.&lt;/p&gt;
&lt;p&gt;There are other options for bundling and more are coming. The &lt;code&gt;go/bundle&lt;/code&gt; CLI
lets you build a Web Bundle using a HAR file or a custom list of resource
URLs. Visit the &lt;a href=&quot;https://github.com/WICG/webpackage/tree/master/go/bundle&quot;&gt;GitHub
repo&lt;/a&gt; to learn more
about &lt;code&gt;go/bundle&lt;/code&gt;. You can also try out the experimental Node.js module for bundling,
&lt;a href=&quot;https://www.npmjs.com/package/wbn&quot;&gt;&lt;code&gt;wbn&lt;/code&gt;&lt;/a&gt;. Note that &lt;code&gt;wbn&lt;/code&gt; is still in the early stages of
development.&lt;/p&gt;
&lt;h2 id=&quot;playing-around-with-web-bundles&quot;&gt;Playing around with Web Bundles &lt;a class=&quot;w-headline-link&quot; href=&quot;#playing-around-with-web-bundles&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To try out a Web Bundle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;chrome://version&lt;/code&gt; to see what version of Chrome you&#39;re running. If you&#39;re running version
80 or later, skip the next step.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download &lt;a href=&quot;https://www.google.com/chrome/canary/&quot;&gt;Chrome Canary&lt;/a&gt; if you&#39;re not running Chrome 80
or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;code&gt;chrome://flags/#web-bundles&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the &lt;strong&gt;Web Bundles&lt;/strong&gt; flag to &lt;strong&gt;Enabled&lt;/strong&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/web-bundles/chromeflag.png&quot; alt=&quot;A screenshot of chrome://flags&quot; style=&quot;max-width: 75%&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Enabling Web Bundles in &lt;code&gt;chrome://flags&lt;/code&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Relaunch Chrome.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drag-and-drop the &lt;code&gt;todomvc.wbn&lt;/code&gt; file into Chrome if you&#39;re on desktop, or tap it in a file
management app if you&#39;re on Android.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Everything magically works.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/web-bundles/preact-todomvc.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The Preact implementation of TodoMVC working offline as a web bundle
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You could also try out other sample web bundles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://storage.googleapis.com/web-dev-assets/web-bundles/web.dev.wbn&quot;&gt;web.dev.wbn&lt;/a&gt; is a
snapshot of the entire web.dev site, as of 2019 October 15.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://storage.googleapis.com/web-dev-assets/web-bundles/proxx.wbn&quot;&gt;proxx.wbn&lt;/a&gt;:
&lt;a href=&quot;https://web.dev/proxx-announce/&quot;&gt;PROXX&lt;/a&gt; is a Minesweeper clone that works offline.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://storage.googleapis.com/web-dev-assets/web-bundles/squoosh.wbn&quot;&gt;squoosh.wbn&lt;/a&gt;:
&lt;a href=&quot;https://squoosh.app/&quot;&gt;Squoosh&lt;/a&gt; is a convenient and fast image optimization tool that
lets you do side-by-side comparisons of various image compression formats, with support for
resizing and format conversions.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Currently you can only navigate into a Web Bundle stored in a local file, but
that&#39;s only a temporary restriction.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;send-feedback&quot;&gt;Send feedback &lt;a class=&quot;w-headline-link&quot; href=&quot;#send-feedback&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Web Bundle API implementation in Chrome is experimental and incomplete.
Not everything is working and it might fail or crash. That&#39;s why
it&#39;s behind an experimental flag. But the API is ready enough for you to explore it in Chrome.
Feedback from web developers is crucial to the design of
new APIs, so please try it out and tell the people working on Web Bundles what you think.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Send general feedback to
&lt;a href=&quot;mailto:webpackage-dev@chromium.org&quot;&gt;webpackage-dev@chromium.org&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you have feedback on the spec visit
&lt;a href=&quot;https://github.com/WICG/webpackage/issues/new&quot;&gt;https://github.com/WICG/webpackage/issues/new&lt;/a&gt;
to file a new spec issue, or email &lt;a href=&quot;mailto:wpack@ietf.org&quot;&gt;wpack@ietf.org&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you find any issues in Chrome&#39;s behavior visit
&lt;a href=&quot;https://crbug.com/new&quot;&gt;https://crbug.com/new&lt;/a&gt; to file a Chromium bug.&lt;/li&gt;
&lt;li&gt;Any contributions to the spec discussion and tooling are also more than
welcome. Visit the &lt;a href=&quot;https://github.com/WICG/webpackage&quot;&gt;spec repo&lt;/a&gt; to get involved.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Acknowledgements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We would like to give a big shout-out to the wonderful Chrome engineering team,
&lt;a href=&quot;https://github.com/irori&quot;&gt;Kunihiko Sakamoto&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/horo&quot;&gt;Tsuyoshi
Horo&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/toyoshim&quot;&gt;Takashi
Toyoshima&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/kinu&quot;&gt;Kinuko
Yasuda&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/jyasskin&quot;&gt;Jeffrey
Yasskin&lt;/a&gt; that worked hard contributing to the
spec, building the feature on Canary and reviewing this article. During the
standardization process &lt;a href=&quot;http://danyork.me/&quot;&gt;Dan York&lt;/a&gt; has helped navigate the
IETF discussion and also &lt;a href=&quot;https://twitter.com/dauwhe&quot;&gt;Dave Cramer&lt;/a&gt; has been a
great resource on what publishers actually need. We also want to thank &lt;a href=&quot;https://twitter.com/_developit&quot;&gt;Jason
Miller&lt;/a&gt; for the amazing preact-todomvc and his
restless effort on making the framework better.&lt;/p&gt;
</content>
<author>
<name>Yusuke Utsunomiya</name>
</author><author>
<name>Kenji Baheux</name>
</author>
</entry>
<entry>
<title>Richer offline experiences with the Periodic Background Sync API</title>
<link href="https://web.dev/periodic-background-sync/"/>
<updated>2019-11-09T16:00:00-08:00</updated>
<id>https://web.dev/periodic-background-sync/</id>
<content type="html">&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Web apps should be able to do anything native apps can. The
&lt;a href=&quot;https://developers.google.com/web/updates/capabilities&quot;&gt;Capabilities project&lt;/a&gt;,
of which Periodic Background Sync is only a part, aims
to do just that. To learn about other capabilities and to keep up with their
progress, follow &lt;a href=&quot;https://developers.google.com/web/updates/capabilities&quot;&gt;Unlocking new capabilities for the
web&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Have you ever been in any of the following situations?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Riding a train or subway with flaky or no connectivity&lt;/li&gt;
&lt;li&gt;Been throttled by your carrier after watching too many videos&lt;/li&gt;
&lt;li&gt;Living in a country where bandwidth struggles to keep up with the demand&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have, then you&#39;ve surely felt the frustration of getting
certain things done on the web, and wondered why native apps so often do better
in these scenarios. Native apps can fetch fresh content such as news articles or weather
information ahead of time. Even if there&#39;s no network in the subway, you can still read the
news.&lt;/p&gt;
&lt;p&gt;Periodic Background Sync enables web applications to periodically synchronize
data in the background, bringing web apps closer to the behavior of a native
app.&lt;/p&gt;
&lt;h2 id=&quot;current-status&quot;&gt;Current status &lt;a class=&quot;w-headline-link&quot; href=&quot;#current-status&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The table below explains the current status of the Periodic Background Sync API.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th markdown=&quot;block&quot;&gt;
Step
&lt;/th&gt;
&lt;th markdown=&quot;block&quot;&gt;
Status
&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
1. Create explainer
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;a href=&quot;https://github.com/WICG/BackgroundSync/tree/master/explainers&quot;&gt;Complete&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
2. Create initial draft of specification
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;a href=&quot;https://github.com/WICG/BackgroundSync/blob/master/explainers/periodicsync-explainer.md&quot; rel=&quot;noopener&quot;&gt;In Progress&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
3. Gather feedback and iterate on design
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
In Progress
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
4. Origin trial
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/4048736065006075905&quot;&gt;Running from Chrome 77 to Chrome 80&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;strong&gt;5. Launch&lt;/strong&gt;
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;strong&gt;Chrome 80&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h2 id=&quot;try-it&quot;&gt;Try it &lt;a class=&quot;w-headline-link&quot; href=&quot;#try-it&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can try periodic background sync with the &lt;a href=&quot;https://webplatformapis.com/periodic_sync/periodicSync_improved.html&quot;&gt;live demo
app&lt;/a&gt;.
Before using it, make sure that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&#39;re using Chrome 80 or later.&lt;/li&gt;
&lt;li&gt;You
&lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/&quot;&gt;install&lt;/a&gt;
the web app before enabling periodic background sync. (The demo app&#39;s
author already took the step of signing up for the origin trial.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;concepts-and-usage&quot;&gt;Concepts and usage &lt;a class=&quot;w-headline-link&quot; href=&quot;#concepts-and-usage&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Periodic background sync lets you show fresh content when a progessive web app
or service worker-backed page is launched. It does this by downloading data in
the background when the app or page is not being used. This prevents the app&#39;s
content from refreshing after launch while it&#39;s being viewed. Better yet, it
prevents the app from showing a content spinner before refreshing.&lt;/p&gt;
&lt;p&gt;Without periodic background sync, web apps must use alternative methods to
download data. A common example is using a push notification to wake a service
worker. The user is interrupted by a message such as &#39;new data available&#39;.
Updating the data is essentially a side effect. You still have the option of
using push notifications for truly important updates, such as significant
breaking news.&lt;/p&gt;
&lt;p&gt;Periodic background sync is easily confused with background sync. Though they
have similar names, their use cases are different. Among other things,
background sync is most commonly used for resending data to a server when a
previous request has failed.&lt;/p&gt;
&lt;h3 id=&quot;getting-this-right&quot;&gt;Getting this right &lt;a class=&quot;w-headline-link&quot; href=&quot;#getting-this-right&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Chrome is putting periodic background sync through a trial period so that you can
help the Chrome team make sure that they get it right. This section explains some of the design
decisions Chrome took to make this feature as helpful as possible.&lt;/p&gt;
&lt;p&gt;The first design decision Chrome made is that a web app can only use periodic
background sync after a person has installed it on their device, and has
launched it as a distinct application. Periodic background sync is not available
in the context of a regular tab in Chrome.&lt;/p&gt;
&lt;p&gt;Furthermore, since Chrome doesn&#39;t want unused or seldom used web apps to gratuitously
consume battery or data, Chrome designed periodic background sync such that
developers will have to earn it by providing value to their users. Concretely,
Chrome is using a &lt;a href=&quot;https://www.chromium.org/developers/design-documents/site-engagement&quot;&gt;site engagement score&lt;/a&gt;
(&lt;code&gt;chrome://site-engagement/&lt;/code&gt;) to determine if and how often periodic background syncs can happen
for a given web app. In other words, a &lt;code&gt;periodicsync&lt;/code&gt; event won&#39;t be fired at all unless the engagement
score is greater than zero, and its value affects the frequency at which the
&lt;code&gt;periodicsync&lt;/code&gt; event fires. This ensures that the only apps syncing in the
background are the ones you are actively using.&lt;/p&gt;
&lt;p&gt;Periodic background sync shares some similarities with existing APIs and
practices on popular platforms. For instance, one-off background sync as well as
push notifications allow a web app&#39;s logic to live a little longer (via its
service worker) after a person has closed the page. On most platforms, it&#39;s
common for people to have installed apps that periodically access the network in
the background to provide a better user experience for critical updates,
prefetching content, syncing data, and so on. Similarly, periodic background sync also
extends the lifetime of a web app&#39;s logic to run at regular periods for what
might be a few minutes at a time.&lt;/p&gt;
&lt;p&gt;If the browser allowed this to occur frequently and without restrictions, it
could result in some privacy concerns. Here&#39;s how Chrome has addressed this
risk for periodic background sync:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The background sync activity only occurs on a network that the device has
previously connected to. Chrome recommends to only connect to networks operated by
trustworthy parties.&lt;/li&gt;
&lt;li&gt;As with all internet communications, periodic background sync reveals the IP
addresses of the client, the server it&#39;s talking to, and the name of the
server. To reduce this exposure to roughly what it would be if the app only
synced when it was in the foreground, the browser limits the frequency of an
app&#39;s background syncs to align with how often the person uses that app. If
the person stops frequently interacting with the app, periodic background sync
will stop triggering. This is a net improvement over the status quo in native
apps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;when-can-it-be-used&quot;&gt;When can it be used? &lt;a class=&quot;w-headline-link&quot; href=&quot;#when-can-it-be-used&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rules for use vary by browser. To summarize from above, Chrome puts the
following requirements on periodic background sync:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A particular user engagement score.&lt;/li&gt;
&lt;li&gt;Presence of a previously used network.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The timing of synchronizations are not controlled by developers. The
synchronization frequency will align with how often the app is used. (Note that
native apps currently don&#39;t do this.) It also takes into the device&#39;s power and
connectivity state.&lt;/p&gt;
&lt;h3 id=&quot;when-should-it-be-used&quot;&gt;When should it be used? &lt;a class=&quot;w-headline-link&quot; href=&quot;#when-should-it-be-used&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When your service worker wakes up to handle a &lt;code&gt;periodicsync&lt;/code&gt; event, you have the
&lt;em&gt;opportunity&lt;/em&gt; to request data, but not the &lt;em&gt;obligation&lt;/em&gt; to do so. When handling
the event you should take network conditions and available storage into
consideration and download different amounts of data in response. You can use
the following resources to help:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API&quot;&gt;Network Information API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/save-data/#detecting_the_save-data_setting&quot;&gt;Detecting data saver mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2017/08/estimating-available-storage-space&quot;&gt;Estimating available storage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;permissions&quot;&gt;Permissions &lt;a class=&quot;w-headline-link&quot; href=&quot;#permissions&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After the service worker is installed, use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API&quot;&gt;Permissions
API&lt;/a&gt; to query
for &lt;code&gt;periodic-background-sync&lt;/code&gt;. You can do this from either a window or a
service worker context.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; status &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permissions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;periodic-background-sync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;granted&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Periodic background sync can be used.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Periodic background sync cannot be used.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;registering-a-periodic-sync&quot;&gt;Registering a periodic sync &lt;a class=&quot;w-headline-link&quot; href=&quot;#registering-a-periodic-sync&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As already stated, periodic background sync requires a service worker. Retrieve
a &lt;code&gt;PeriodicSyncManager&lt;/code&gt; using &lt;code&gt;ServiceWorkerRegistration.periodicSync&lt;/code&gt; and call
&lt;code&gt;register()&lt;/code&gt; on it. Registering requires both a tag and a minimum
synchronization interval (&lt;code&gt;minInterval&lt;/code&gt;). The tag identifies the registered sync
so that multiple syncs can be registered. In the example below, the tag name is
&lt;code&gt;&#39;content-sync&#39;&lt;/code&gt; and the &lt;code&gt;minInterval&lt;/code&gt; is one day.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;periodicSync&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;periodicSync&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content-sync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// An interval of one day.&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; minInterval&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Periodic background sync cannot be used.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;verifying-a-registration&quot;&gt;Verifying a registration &lt;a class=&quot;w-headline-link&quot; href=&quot;#verifying-a-registration&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Call &lt;code&gt;periodicSync.getTags()&lt;/code&gt; to retrieve an array of registration tags. The
example below uses tag names to confirm that cache updating is active to avoid
updating again.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;periodicSync&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tags &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;periodicSync&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Only update content if sync isn&#39;t set up.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;tags&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content-sync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;updateContentOnPageLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// If periodic background sync isn&#39;t supported, always update.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;updateContentOnPageLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;You can also use &lt;code&gt;getTags()&lt;/code&gt; to show a list of active registrations in your web
app&#39;s settings page so that users can enable or disable specific types of
updates.&lt;/p&gt;
&lt;h3 id=&quot;responding-to-a-periodic-background-sync-event&quot;&gt;Responding to a periodic background sync event &lt;a class=&quot;w-headline-link&quot; href=&quot;#responding-to-a-periodic-background-sync-event&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To respond to a periodic background sync event add a &lt;code&gt;periodicsync&lt;/code&gt; event
handler to your service worker. The &lt;code&gt;event&lt;/code&gt; object passed to it will contain a
&lt;code&gt;tag&lt;/code&gt; parameter matching the value used during registration. For example if a
periodic background sync was registered with the name &lt;code&gt;&#39;content-sync&#39;&lt;/code&gt;, then
&lt;code&gt;event.tag&lt;/code&gt; will be &lt;code&gt;&#39;content-sync&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;periodicsync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;content-sync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// See the &quot;Think before you sync&quot; section for&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// checks you could perform before syncing.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitUntil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;syncContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Other logic for different tags as needed.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;unregistering-a-sync&quot;&gt;Unregistering a sync &lt;a class=&quot;w-headline-link&quot; href=&quot;#unregistering-a-sync&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To end a registered sync, call &lt;code&gt;periodicSync.unregister()&lt;/code&gt; with the name of the
sync you want to unregister.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;periodicSync&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;periodicSync&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unregister&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;content-sync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;interfaces&quot;&gt;Interfaces &lt;a class=&quot;w-headline-link&quot; href=&quot;#interfaces&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here&#39;s a quick run down of the interfaces provided by the Periodic Background
Sync API.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PeriodicSyncEvent&lt;/code&gt;. Passed to the &lt;code&gt;ServiceWorkerGlobalScope.onperiodicsync&lt;/code&gt; event handler at a
time of the browser&#39;s choosing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PeriodicSyncManager&lt;/code&gt;. Registers and unregisters periodic syncs and provides tags for registered
syncs. Retrieve an instance of this class from the ServiceWorkerRegistration.periodicSync`
property.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ServiceWorkerGlobalScope.onperiodicsync&lt;/code&gt;. Registers a handler to receive the &lt;code&gt;PeriodicSyncEvent&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ServiceWorkerRegistration.periodicSync&lt;/code&gt;. Returns a reference to the &lt;code&gt;PeriodicSyncManager&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;example&quot;&gt;Example &lt;a class=&quot;w-headline-link&quot; href=&quot;#example&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;updating-content&quot;&gt;Updating content &lt;a class=&quot;w-headline-link&quot; href=&quot;#updating-content&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following example uses periodic background sync to download and cache up-to-date articles for a news site or blog. Notice the tag name, which indicates the kind of sync this is (&lt;code&gt;&#39;update-articles&#39;&lt;/code&gt;). The call to &lt;code&gt;updatearticles()&lt;/code&gt; is wrapped in &lt;code&gt;event.waitUntil()&lt;/code&gt; so that the service worker won&#39;t terminate before the articles are downloaded and stored.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateArticles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; articlesCache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;articles&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; articlesCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/api/articles&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;periodicsync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;update-articles&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitUntil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateArticles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;debugging&quot;&gt;Debugging &lt;a class=&quot;w-headline-link&quot; href=&quot;#debugging&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It can be a challenge to get and end-to-end view of periodic background sync
while testing locally. Information about active registrations, approximate sync
intervals, and logs of past sync events provide valuable context while debugging
your web app&#39;s behavior. Fortunately, you can find all of that information
through an experimental feature in Chrome DevTools.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Periodic background sync debugging is currently disabled by default. See &lt;a href=&quot;https://developers.google.com/web/updates/2019/08/periodic-background-sync#enabling_the_devtools_interface&quot;&gt;Enabling the DevTools
interface&lt;/a&gt;
for the steps needed to enable it during the origin trial.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;recording-local-activity&quot;&gt;Recording local activity &lt;a class=&quot;w-headline-link&quot; href=&quot;#recording-local-activity&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Periodic Background Sync&lt;/strong&gt; section of DevTools is organized around key events
in the periodic background sync lifecycle: registering for sync, performing a
background sync, and unregistering. To obtain information about these events,
click &lt;strong&gt;Start recording&lt;/strong&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/periodic-background-sync/1-record.png&quot; alt=&quot;The record button in DevTools&quot; style=&quot;max-width: 75%&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The record button in DevTools
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While recording, entries will appear in DevTools corresponding to events, with
context and metadata logged for each.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/periodic-background-sync/2-record-result.png&quot; alt=&quot;An example of recorded periodic background sync data&quot; style=&quot;max-width: 75%&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
An example of recorded periodic background sync data
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After enabling recording once, it will stay enabled for up to three days,
allowing DevTools to capture local debugging information about background syncs
that might take place, even hours in the future.&lt;/p&gt;
&lt;h3 id=&quot;simulating-events&quot;&gt;Simulating events &lt;a class=&quot;w-headline-link&quot; href=&quot;#simulating-events&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While recording background activity can be helpful, there are times when you&#39;ll
want to test your &lt;code&gt;periodicsync&lt;/code&gt; handler immediately, without waiting for an
event to fire on its normal cadence.&lt;/p&gt;
&lt;p&gt;You can do this via the &lt;strong&gt;Service Workers&lt;/strong&gt; section within the Application panel in
Chrome DevTools. The &lt;strong&gt;Periodic Sync&lt;/strong&gt; field allows you to provide a tag for the
event to use, and to trigger it as many times as you&#39;d like.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Manually triggering a &lt;code&gt;periodicsync&lt;/code&gt; event requires Chrome 78 or later.
You&#39;ll need to follow the same &lt;a href=&quot;https://developers.google.com/web/updates/2019/08/periodic-background-sync#enabling_the_devtools_interface&quot;&gt;Enabling the DevTools
interface&lt;/a&gt;
steps to turn it on.&lt;/p&gt;
&lt;/div&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/periodic-background-sync/3-sw-panel.png&quot; alt=&quot;The &#39;Service Workers&#39; section of the Application panel shows a &#39;Periodic Sync&#39;
text field and button.&quot; style=&quot;max-width: 90%&quot;&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;using-the-devtools-interface&quot;&gt;Using the DevTools interface &lt;a class=&quot;w-headline-link&quot; href=&quot;#using-the-devtools-interface&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Starting in Chrome 80, you&#39;ll see a &lt;strong&gt;Periodic Background Sync&lt;/strong&gt; section in the
DevTools &lt;em&gt;Application&lt;/em&gt; panel.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/periodic-background-sync/7-panel.png&quot; alt=&quot;The Application panel showing the Periodic Background Sync section&quot; style=&quot;max-width: 75%&quot;&gt;
&lt;/figure&gt;
</content>
<author>
<name>Jeff Posnick</name>
</author><author>
<name>Joe Medley</name>
</author>
</entry>
<entry>
<title>Share like a native app with the Web Share API</title>
<link href="https://web.dev/web-share/"/>
<updated>2019-11-07T16:00:00-08:00</updated>
<id>https://web.dev/web-share/</id>
<content type="html">&lt;p&gt;With the Web Share API, web apps are able to use the same system-provided share
capabilities as native apps. The Web Share API makes it possible for web apps to
share links, text, and files to other apps installed on the device in the same
way as native apps.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Sharing is only half of the magic. Web apps can also be share
targets, meaning they can receive data, links, text, and files from
native or web apps. See the &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Receive shared data&lt;/a&gt;
post for details on how to register your app as a share target.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;concepts-and-usage&quot;&gt;Concepts and usage &lt;a class=&quot;w-headline-link&quot; href=&quot;#concepts-and-usage&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;img src=&quot;https://web.dev/web-share/wst-send.png&quot; style=&quot;max-width: 370px&quot; alt=&quot;System-level share target picker with an installed PWA as an option.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
System-level share target picker with an installed PWA as an option.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;capabilities-and-limitations&quot;&gt;Capabilities and limitations &lt;a class=&quot;w-headline-link&quot; href=&quot;#capabilities-and-limitations&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Web share has the following capabilities and limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It can only be used on a site that supports HTTPS.&lt;/li&gt;
&lt;li&gt;It must be invoked in response to a user action such as a click. Invoking it
through the &lt;code&gt;onload&lt;/code&gt; handler is impossible.&lt;/li&gt;
&lt;li&gt;It can share, URLs, text, or files.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;h3 id=&quot;sharing-links-and-text&quot;&gt;Sharing links and text &lt;a class=&quot;w-headline-link&quot; href=&quot;#sharing-links-and-text&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To share links and text, use the &lt;code&gt;share()&lt;/code&gt; method, which is a promise-based
method with a required properties object.
To keep the browser from throwing a &lt;code&gt;TypeError&lt;/code&gt;,
the object must contain at least one
of the following properties: &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt; or &lt;code&gt;files&lt;/code&gt;. You
can, for example, share text without a URL or vice versa. Allowing all three
members expands the flexibility of use cases. Imagine if after running the code
below, the user chose an email application as the target. The &lt;code&gt;title&lt;/code&gt; parameter
might become the email subject, the &lt;code&gt;text&lt;/code&gt;, the message body, and the files, the
attachments.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;share&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web.dev&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Check out web.dev.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://web.dev/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Successful share&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Error sharing&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;If your site has multiple URLs for the same content, share the page&#39;s
canonical URL instead of the current URL. Instead of sharing
&lt;code&gt;document.location.href&lt;/code&gt;, you would check for a canonical URL &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag in
the page&#39;s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and share that. This will provide a better experience to the
user. Not only does it avoid redirects, but it also ensures that a shared URL serves
the correct user experience for a particular client. For example, if a friend
shares a mobile URL and you look at it on a desktop computer,
you should see a desktop version:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canonicalElement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;link[rel=canonical]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canonicalElement &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canonicalElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;sharing-files&quot;&gt;Sharing files &lt;a class=&quot;w-headline-link&quot; href=&quot;#sharing-files&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To share files, first test for and call &lt;code&gt;navigator.canShare()&lt;/code&gt;. Then include an
array of files in the call to &lt;code&gt;navigator.share()&lt;/code&gt;:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;canShare &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;canShare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; files&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; filesArray &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; files&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; filesArray&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Vacation Pictures&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; text&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Photos from September 27 to October 14.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Share was successful.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Sharing failed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;`Your system doesn&#39;t support sharing files.`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Notice that the sample handles feature detection by testing for
&lt;code&gt;naviagator.canShare()&lt;/code&gt; rather than for &lt;code&gt;navigator.share()&lt;/code&gt;.
The data object passed to &lt;code&gt;canShare()&lt;/code&gt; only supports the &lt;code&gt;files&lt;/code&gt; property.
Image, video, audio, and text files can be shared. (See
&lt;a href=&quot;https://docs.google.com/document/d/1tKPkHA5nnJtmh2TgqWmGSREUzXgMUFDL6yMdVZHqUsg/edit?usp=sharing&quot;&gt;Permitted File Extensions in Chromium&lt;/a&gt;.)
More file types may be added in the future.&lt;/p&gt;
&lt;h2 id=&quot;santa-tracker-case-study&quot;&gt;Santa Tracker case study &lt;a class=&quot;w-headline-link&quot; href=&quot;#santa-tracker-case-study&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;img src=&quot;https://web.dev/web-share/santa-phone.png&quot; style=&quot;max-width: 400px;&quot; alt=&quot;The Santa Tracker app showing a share button.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
Santa Tracker share button.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://santatracker.google.com/&quot;&gt;Santa Tracker&lt;/a&gt;, an open-source project, is a
holiday tradition at Google. Every December, you can celebrate the season
with games and educational experiences.&lt;/p&gt;
&lt;p&gt;In 2016, the Santa Tracker team used the Web Share API on Android.
This API was a perfect fit for mobile.
In previous years, the team disabled share buttons on mobile because space is
at a premium, and they couldn&#39;t justify having several share targets.&lt;/p&gt;
&lt;p&gt;But with the Web Share API, they were able to present just one button,
saving precious pixels.
They also found that users shared with Web Share around 20% more than
users without the API enabled. Head to
&lt;a href=&quot;https://santatracker.google.com/&quot;&gt;Santa Tracker&lt;/a&gt; to see Web Share in action.&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;h2 id=&quot;helpful-links&quot;&gt;Helpful Links &lt;a class=&quot;w-headline-link&quot; href=&quot;#helpful-links&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://w3c.github.io/web-share/demos/share-files.html&quot;&gt;Web Share Demos&lt;/a&gt;&lt;/p&gt;
</content>
<author>
<name>Joe Medley</name>
</author>
</entry>
<entry>
<title>Receiving shared data with the Web Share Target API</title>
<link href="https://web.dev/web-share-target/"/>
<updated>2019-11-07T16:00:00-08:00</updated>
<id>https://web.dev/web-share-target/</id>
<content type="html">&lt;p&gt;On a mobile device, sharing should be as simple as clicking the &lt;strong&gt;Share&lt;/strong&gt; button,
choosing an app, and choosing who to share with. For example, you may want to
share an interesting article, either by emailing it to friends or tweeting it to
the world.&lt;/p&gt;
&lt;p&gt;In the past, only native apps could register with the operating system to
receive shares from other installed apps. But with the Web Share Target API,
installed web apps can register with the underlying operating system
as a share target to receive shared content.
Support for text and data was added in Chrome 71, and
support for files was added in Chrome 76.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The Web Share Target API is only half of the magic. Web apps can share data,
files, links, or text using the Web Share API. See
&lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Share like a native&lt;/a&gt; for details.&lt;/p&gt;
&lt;/div&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;img src=&quot;https://web.dev/web-share-target/wst-send.png&quot; style=&quot;max-width: 400px;&quot; alt=&quot;Android phone with the &#39;Share via&#39; drawer open.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
System-level share target picker with an installed PWA as an option.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;see-web-share-target-in-action&quot;&gt;See Web Share Target in action &lt;a class=&quot;w-headline-link&quot; href=&quot;#see-web-share-target-in-action&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Using Chrome 76 or later (Android only), open the &lt;a href=&quot;https://web-share.glitch.me/&quot;&gt;Web Share Target demo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;When prompted, click &lt;strong&gt;Install&lt;/strong&gt; to add the app to your home screen, or
use the Chrome menu to add it to your home screen.&lt;/li&gt;
&lt;li&gt;Open any app that supports native sharing, or use the Share button
in the demo app.&lt;/li&gt;
&lt;li&gt;From the target picker, choose &lt;strong&gt;Web Share Test&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After sharing, you should see all of the shared information in
the web share target web app.&lt;/p&gt;
&lt;h2 id=&quot;register-your-app-as-a-share-target&quot;&gt;Register your app as a share target &lt;a class=&quot;w-headline-link&quot; href=&quot;#register-your-app-as-a-share-target&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To register your app as a share target, it needs to meet &lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/#criteria&quot;&gt;Chrome&#39;s
installability criteria&lt;/a&gt;. In addition, before a user can share
to your app, they must add it to their home screen. This prevents sites from
randomly adding themselves to the user&#39;s share intent chooser and ensures that
sharing is something that users want to do with your app.&lt;/p&gt;
&lt;h2 id=&quot;update-your-web-app-manifest&quot;&gt;Update your web app manifest &lt;a class=&quot;w-headline-link&quot; href=&quot;#update-your-web-app-manifest&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To register your app as a share target, add a &lt;code&gt;share_target&lt;/code&gt; entry to its &lt;a href=&quot;https://web.dev/add-manifest/&quot;&gt;web
app manifest&lt;/a&gt;. This tells the operating system to include your app as
an option in the intent chooser. What you add to the manifest controls the data
that your app will accept. There are three common scenarios for the &lt;code&gt;share_target&lt;/code&gt;
entry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accepting basic information&lt;/li&gt;
&lt;li&gt;Accepting application changes&lt;/li&gt;
&lt;li&gt;Accepting files&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;You can only have one &lt;code&gt;share_target&lt;/code&gt; per manifest, if you want to share to
different places within your app, provide that as an option within the share
target landing page.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;accepting-basic-information&quot;&gt;Accepting basic information &lt;a class=&quot;w-headline-link&quot; href=&quot;#accepting-basic-information&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If your target app is merely accepting basic information such as data, links,
and text, add the following to the &lt;code&gt;manifest.json&lt;/code&gt; file:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;share_target&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/share-target/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;If your application already has a share URL scheme, you can replace the &lt;code&gt;param&lt;/code&gt;
values with your existing query parameters. For example, if your share URL
scheme uses &lt;code&gt;body&lt;/code&gt; instead of &lt;code&gt;text&lt;/code&gt;, you could replace &lt;code&gt;&amp;quot;text&amp;quot;: &amp;quot;text&amp;quot;&lt;/code&gt; with &lt;code&gt;&amp;quot;text&amp;quot;: &amp;quot;body&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;method&lt;/code&gt; value defaults to &lt;code&gt;&amp;quot;GET&amp;quot;&lt;/code&gt; if not provided. The &lt;code&gt;enctype&lt;/code&gt; field, not
shown in this example, indicates the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype&quot;&gt;type of encoding&lt;/a&gt; for the data.
For the &lt;code&gt;&amp;quot;GET&amp;quot;&lt;/code&gt; method, &lt;code&gt;enctype&lt;/code&gt; defaults to &lt;code&gt;&amp;quot;application/x-www-form-urlencoded&amp;quot;&lt;/code&gt; and
is ignored if it&#39;s set to anything else.&lt;/p&gt;
&lt;h3 id=&quot;accepting-application-changes&quot;&gt;Accepting application changes &lt;a class=&quot;w-headline-link&quot; href=&quot;#accepting-application-changes&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If the shared data changes the target app in some way—for example, saving a
bookmark in the target application—set the &lt;code&gt;method&lt;/code&gt; value to &lt;code&gt;&amp;quot;POST&amp;quot;&lt;/code&gt; and include
the &lt;code&gt;enctype&lt;/code&gt; field. The example below creates a bookmark in the target app,
so it uses &lt;code&gt;&amp;quot;POST&amp;quot;&lt;/code&gt; for the &lt;code&gt;method&lt;/code&gt; and &lt;code&gt;&amp;quot;multipart/form-data&amp;quot;&lt;/code&gt; for the
&lt;code&gt;enctype&lt;/code&gt;:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Bookmark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;share_target&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/bookmark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;enctype&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;multipart/form-data&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;link&quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;accepting-files&quot;&gt;Accepting files &lt;a class=&quot;w-headline-link&quot; href=&quot;#accepting-files&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As with application changes, accepting files requires that &lt;code&gt;method&lt;/code&gt; be &lt;code&gt;&amp;quot;POST&amp;quot;&lt;/code&gt;
and that &lt;code&gt;enctype&lt;/code&gt; be present. Additionally, &lt;code&gt;enctype&lt;/code&gt; must be
&lt;code&gt;&amp;quot;multipart/form-data&amp;quot;&lt;/code&gt;, and a &lt;code&gt;files&lt;/code&gt; entry must be added.&lt;/p&gt;
&lt;p&gt;You must also add a &lt;code&gt;files&lt;/code&gt; array defining the types of files your app accepts. The
array elements are entries with two members: a &lt;code&gt;name&lt;/code&gt; field and an &lt;code&gt;accept&lt;/code&gt;
field. The &lt;code&gt;accept&lt;/code&gt; field takes a MIME type, a file extension, or an array
containing both.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Aggregator&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;share_target&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/cgi-bin/aggregate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;enctype&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;multipart/form-data&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;link&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;files&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;records&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;accept&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text/csv&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.csv&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;graphs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;accept&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/svg+xml&quot;&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;handle-the-incoming-content&quot;&gt;Handle the incoming content &lt;a class=&quot;w-headline-link&quot; href=&quot;#handle-the-incoming-content&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;How you deal with the incoming shared data is up to you and depends on your
app. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An email client could draft a new email using &lt;code&gt;title&lt;/code&gt; as the subject of an
email, with &lt;code&gt;text&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt; concatenated together as the body.&lt;/li&gt;
&lt;li&gt;A social networking app could draft a new post ignoring &lt;code&gt;title&lt;/code&gt;, using
&lt;code&gt;text&lt;/code&gt; as the body of the message, and adding &lt;code&gt;url&lt;/code&gt; as a link. If &lt;code&gt;text&lt;/code&gt; is
missing, the app might use &lt;code&gt;url&lt;/code&gt; in the body as well. If &lt;code&gt;url&lt;/code&gt; is missing,
the app might scan &lt;code&gt;text&lt;/code&gt; looking for a URL and add that as a link.&lt;/li&gt;
&lt;li&gt;A photo sharing app could create a new slideshow using &lt;code&gt;title&lt;/code&gt; as the
slideshow title, &lt;code&gt;text&lt;/code&gt; as a description, and &lt;code&gt;files&lt;/code&gt; as the slideshow images.&lt;/li&gt;
&lt;li&gt;A text messaging app could draft a new message using &lt;code&gt;text&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt;
concatenated together and dropping &lt;code&gt;title&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;processing-get-shares&quot;&gt;Processing GET shares &lt;a class=&quot;w-headline-link&quot; href=&quot;#processing-get-shares&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If the user selects your application, and your &lt;code&gt;method&lt;/code&gt; is &lt;code&gt;&amp;quot;GET&amp;quot;&lt;/code&gt; (the
default), the browser opens a new window at the &lt;code&gt;action&lt;/code&gt; URL. The browser then
generates a query string using the URL-encoded values supplied in the manifest.
For example, if the sharing app provides &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt;, the query string is
&lt;code&gt;?title=hello&amp;amp;text=world&lt;/code&gt;. To process this, use a &lt;code&gt;DOMContentLoaded&lt;/code&gt; event
listener in your foreground page and parse the query string:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;DOMContentLoaded&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parsedUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// searchParams.get() will properly handle decoding the values.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Title shared: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; parsedUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;title&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Text shared: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; parsedUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;URL shared: &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; parsedUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;url&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Be sure to use a service worker to precache the &lt;code&gt;action&lt;/code&gt; page so that it will
load quickly and work reliably, even if the user is offline.&lt;/p&gt;
&lt;h3 id=&quot;processing-post-shares&quot;&gt;Processing POST shares &lt;a class=&quot;w-headline-link&quot; href=&quot;#processing-post-shares&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If your &lt;code&gt;method&lt;/code&gt; is &lt;code&gt;&amp;quot;POST&amp;quot;&lt;/code&gt;, as it would be if your target app accepts a saved
bookmark or shared files, then the body of the incoming &lt;code&gt;POST&lt;/code&gt; request contains
the data passed by the sharing application, encoded using the &lt;code&gt;enctype&lt;/code&gt; value
provided in the manifest.&lt;/p&gt;
&lt;p&gt;The foreground page cannot process this data directly. Since the page sees the data as
a request, the page passes it to the service worker, where you can intercept it with a
&lt;code&gt;fetch&lt;/code&gt; event listener. From here, you can pass the data back to the foreground
page using &lt;code&gt;postMessage()&lt;/code&gt; or pass it on to the server:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; formData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; link &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; formData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;link&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responseUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveBookmark&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;responseUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;303&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;verifying-shared-content&quot;&gt;Verifying shared content &lt;a class=&quot;w-headline-link&quot; href=&quot;#verifying-shared-content&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;img src=&quot;https://web.dev/web-share-target/wst-receive.png&quot; style=&quot;max-width: 400px;&quot; alt=&quot;An Android phone displaying the demo app with shared content.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
The sample sharing target app.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Be sure to verify incoming data. Unfortunately, there is no guarantee that other
apps will share the appropriate content in the right parameter.&lt;/p&gt;
&lt;p&gt;For example, on Android, the &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=789379&quot;&gt;&lt;code&gt;url&lt;/code&gt; field will be
empty&lt;/a&gt; because
it&#39;s not supported in Android&#39;s share system. Instead, URLs will often appear in
the &lt;code&gt;text&lt;/code&gt; field, or occasionally in the &lt;code&gt;title&lt;/code&gt; field.&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
</content>
<author>
<name>Pete LePage</name>
</author><author>
<name>Joe Medley</name>
</author>
</entry>
<entry>
<title>OYO Lite: a TWA with the best of web and Android apps</title>
<link href="https://web.dev/oyo-lite-twa/"/>
<updated>2019-11-06T16:00:00-08:00</updated>
<id>https://web.dev/oyo-lite-twa/</id>
<content type="html">&lt;p&gt;Founded in 2013, &lt;a href=&quot;https://www.oyorooms.com/&quot;&gt;OYO Rooms&lt;/a&gt;
has become one of India&#39;s largest hospitality companies,
with hotels across hundreds of cities in more than 80 countries.
That success came in part from making their online reservation experience
as fast and easy as possible.&lt;/p&gt;
&lt;p&gt;Until recently, the OYO team was offering both a
&lt;a href=&quot;https://developers.google.com/web/progressive-web-apps&quot;&gt;Progressive Web App (PWA)&lt;/a&gt;
and a native Android app to achieve that goal.
The Android app had significantly higher engagement:
users converted three times as often as those using the PWA.
But users also tended to uninstall the Android app over time
because of concerns about storage space.&lt;/p&gt;
&lt;p&gt;To reduce the Android app&#39;s footprint on users&#39; devices
while keeping the benefits of the native experience,
the team decided to turn to
&lt;a href=&quot;https://developers.google.com/web/updates/2019/02/using-twa&quot;&gt;Trusted Web Activities (TWAs)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what&#39;s-a-twa&quot;&gt;What&#39;s a TWA? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what&#39;s-a-twa&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before Chrome 72, Android developers who wanted to display web content in
their native apps had to use
&lt;a href=&quot;https://developer.android.com/reference/android/webkit/WebView&quot;&gt;WebView&lt;/a&gt;,
which came with some meaningful limitations:
it&#39;s not as fast as Chrome and doesn&#39;t include all of Chrome&#39;s APIs and features.
So, if you wanted behavior that WebView&#39;s rendering engine didn&#39;t support,
you had to build your own browser around it—which isn&#39;t exactly trivial!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2019/02/using-twa&quot;&gt;Trusted Web Activities (TWAs)&lt;/a&gt;
address those limitations by showing web content directly in Chrome.
Breaking down the TWA name helps explain its features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;em&gt;activity&lt;/em&gt; is one screen or view in an Android app&#39;s user interface.&lt;/li&gt;
&lt;li&gt;TWAs use Chrome to display &lt;em&gt;web&lt;/em&gt; content for the app&#39;s activities.&lt;/li&gt;
&lt;li&gt;A TWA&#39;s content is &lt;em&gt;trusted&lt;/em&gt; because it uses
&lt;a href=&quot;https://developers.google.com/digital-asset-links/v1/getting-started&quot;&gt;Digital Asset Links&lt;/a&gt;
to verify that the same person created the Android app
and the web content that it&#39;s displaying.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;It&#39;s easy to confuse PWAs and TWAs.
&lt;em&gt;PWAs&lt;/em&gt; use web technologies to create experiences comparable to native apps.
&lt;em&gt;TWAs&lt;/em&gt; allow you to display a PWA in an Android app wrapper
that can be downloaded from the Google Play Store.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;why-create-a-twa-rather-than-a-native-app&quot;&gt;Why create a TWA rather than a native app? &lt;a class=&quot;w-headline-link&quot; href=&quot;#why-create-a-twa-rather-than-a-native-app&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;More and more Android apps are delivering content from developers&#39; own websites.
TWAs acknowledge that reality by offering the best
of the native and web app worlds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They have all the expected functionality of native Android apps,
including a launcher icon, push notifications, and fullscreen display.&lt;/li&gt;
&lt;li&gt;They offer the performance and features of Chrome.&lt;/li&gt;
&lt;li&gt;They use the version of Chrome installed on the device,
so they always have the latest APIs and features.&lt;/li&gt;
&lt;li&gt;They use significantly less storage than a fully native app,
which is a concern for many users,
especially those with lower-end devices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Running in Chrome also has a number of handy perks.
For example, TWAs share Chrome&#39;s storage,
including cookies, passwords, and anything stored using the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API&quot;&gt;Web Storage API&lt;/a&gt;.
One benefit of this setup is that users stay logged in
across the browser and the TWA app.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;TWA support will soon be available in several Android browsers;
both Samsung and FireFox have committed to TWA.
You can specify what browser you want your app to use,
though it&#39;s best to select the user&#39;s default browser.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;building-oyo-lite&quot;&gt;Building OYO Lite &lt;a class=&quot;w-headline-link&quot; href=&quot;#building-oyo-lite&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The OYO team wanted to give their users a native app experience
without having to compromise on device storage,
so they decided to create
&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.oyo.consumerlite&quot;&gt;OYO Lite&lt;/a&gt;,
a TWA built on their existing PWA.&lt;/p&gt;
&lt;p&gt;Starting with a PWA is essential.
Users expect a native-like experience in an app they run from the Android launcher,
so web content served in a TWA must provide that experience, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fast load and response times&lt;/li&gt;
&lt;li&gt;Reliability when the user has limited or no connectivity&lt;/li&gt;
&lt;li&gt;A unified look and feel (by providing, for example, a splash screen and app color)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Meeting the &lt;a href=&quot;https://web.dev/lighthouse-pwa&quot;&gt;Lighthouse requirements for a PWA&lt;/a&gt;
is actually a prerequisite for TWAs.
To learn more about building a PWA,
see the &lt;a href=&quot;https://web.dev/installable&quot;&gt;Installable collection&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;If you already have a PWA,
the steps for creating a basic TWA are designed to be low-effort,
even if you&#39;ve never developed for Android before.
Here&#39;s what the OYO team did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Created an
&lt;a href=&quot;https://developer.android.com/guide/topics/manifest/manifest-intro&quot;&gt;Android manifest file&lt;/a&gt;
containing a &lt;code&gt;DEFAULT_URL&lt;/code&gt; and
&lt;a href=&quot;https://developer.android.com/guide/components/intents-filters&quot;&gt;intent filters&lt;/a&gt;
to allow the app to display content from &lt;a href=&quot;https://oyorooms.com/&quot;&gt;oyorooms.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Removed the browser&#39;s URL bar using
&lt;a href=&quot;https://developers.google.com/digital-asset-links/v1/getting-started&quot;&gt;Digital Asset Link&lt;/a&gt;
verification.&lt;/li&gt;
&lt;li&gt;Created a launcher icon.&lt;/li&gt;
&lt;li&gt;Created a custom splash screen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And here&#39;s the result:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/oyo-case-study/oyo-lite.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/oyo-case-study/oyo-lite.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
OYO Lite in action.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For a more detailed walkthrough of building a basic TWA,
check out Peter McLachlan and Andre Bandarra&#39;s
&lt;a href=&quot;https://youtu.be/6lHBw3F4cWs&quot;&gt;TWA talk from Google I/O 2019&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To learn more about how the OYO team created OYO Lite—including
a deep dive into their approach for creating a splash screen
that&#39;s accessible to all Android users—take a look at Ankit Jain&#39;s
&lt;a href=&quot;https://medium.com/@ankitjainaj/3dd327d7afc5&quot;&gt;post on Medium&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-a-twa-helped-oyo-meet-the-needs-of-its-users&quot;&gt;How a TWA helped OYO meet the needs of its users &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-a-twa-helped-oyo-meet-the-needs-of-its-users&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By storing most of their app assets in Chrome&#39;s cache,
the OYO team was able to get the initial download size
for OYO Lite down to a svelte 850 KB.
That&#39;s just 7% the size of their native Android app!&lt;/p&gt;
&lt;p&gt;That small footprint combined with the amenities of a native app
downloadable from the Google Play Store led to significant gains
in user engagement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A conversion rate three times higher than the PWA&#39;s rate&lt;/li&gt;
&lt;li&gt;Three times more logged-in users than the PWA, on average&lt;/li&gt;
&lt;li&gt;A 4.1 rating on the Google Play Store&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And aside from the user-facing benefits,
going with a TWA meant the team had only one codebase,
which they could easily update without having to wait
for users to download the latest version of the app.&lt;/p&gt;
&lt;h2 id=&quot;build-your-own-twa&quot;&gt;Build your own TWA &lt;a class=&quot;w-headline-link&quot; href=&quot;#build-your-own-twa&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OYO&#39;s online reservation platform is only one use case for TWAs.
They can be a great fit for many projects currently built as native apps or web pages,
from shopping carts and checkout flows to FAQs and contact forms.&lt;/p&gt;
&lt;p&gt;Check out these links to get started with TWAs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2019/02/using-twa&quot;&gt;Using Trusted Web Activities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/6lHBw3F4cWs&quot;&gt;Taking Chrome Full Screen with Trusted Web Activities (YouTube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@ankitjainaj/3dd327d7afc5&quot;&gt;A complete guide to Trusted Web Activity (TWA): OYO case study&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
<author>
<name>Ankit Jain</name>
</author><author>
<name>Michael Friesenhahn</name>
</author>
</entry>
<entry>
<title>Five ways AirSHIFT improved their React app&#39;s runtime performance</title>
<link href="https://web.dev/five-ways-airshift-improved-their-react-app/"/>
<updated>2019-11-05T16:00:00-08:00</updated>
<id>https://web.dev/five-ways-airshift-improved-their-react-app/</id>
<content type="html">&lt;p&gt;Website performance is not just about load time. It is critical to provide a fast and responsive experience to users, especially for productivity desktop apps which people use everyday. The engineering team at &lt;a href=&quot;https://recruit-tech.co.jp/&quot;&gt;Recruit Technologies&lt;/a&gt; went through a refactoring project to improve one of their web apps, &lt;a href=&quot;https://airregi.jp/shift/&quot;&gt;AirSHIFT&lt;/a&gt;, for better user input performance. Here&#39;s how they did it.&lt;/p&gt;
&lt;h2 id=&quot;slow-response-less-productivity&quot;&gt;Slow response, less productivity &lt;a class=&quot;w-headline-link&quot; href=&quot;#slow-response-less-productivity&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AirSHIFT is a desktop web application that helps store owners, like restaurants and cafes, to manage the shift work of their staff members. Built with React, the single page application provides rich client features including various grid tables of shift schedules organized by day, week, month and more.&lt;/p&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/airshift_visual.png&quot; alt=&quot;A screenshot of the AirSHIFT web app.&quot;&gt;
&lt;p&gt;As the Recruit Technologies engineering team added new features to the
AirSHIFT app, they started seeing more feedback around slow performance.
The engineering manager of AirSHIFT, Yosuke Furukawa, said:&lt;/p&gt;
&lt;blockquote class=&quot;w-blockquote&quot;&gt;
In a user research study, we were shocked when one of the store owners said she
would leave her seat to brew coffee after clicking a button, just to kill time waiting
for the shift table to load.
&lt;/blockquote&gt;
&lt;p&gt;After going through the research, the engineering team realized that many of their users were trying to load massive shift tables on low spec computers, such as a 1 GHz Celeron M laptop from 10 years ago.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/endless_spinner_vp9.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/endless_spinner_h264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Endless spinner on low end devices.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The AirSHIFT app was blocking the main thread with expensive scripts,
but the engineering team didn&#39;t realize how expensive the scripts were because they
were developing and testing on rich spec computers with fast Wi-Fi connections.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/main-thread-break-down.png&quot; alt=&quot;A chart that shows the app&#39;s runtime activity.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
When loading the shift table, around 80% of the load time was consumed by running scripts.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After profiling their performance in Chrome DevTools with CPU and network throttling enabled,
it became clear that performance optimization was needed.
AirSHIFT formed a task force to tackle this issue. Here are 5 things
they focused on to make their app more responsive to user input.&lt;/p&gt;
&lt;h2 id=&quot;1.-virtualize-large-tables&quot;&gt;1. Virtualize large tables &lt;a class=&quot;w-headline-link&quot; href=&quot;#1.-virtualize-large-tables&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Displaying the shift table required multiple expensive steps: constructing the virtual DOM and rendering it on screen in proportion to the number of staff members and time slots. For example, if a restaurant had 50 working members and wanted to check their monthly shift schedule, it would be a table of 50 (members) multiplied by 30 (days) which would lead to 1,500 cell components to render. This is a very expensive operation, especially for low spec devices. In reality, things were worse. From the research they learned there were shops managing 200 staff members, requiring around 6,000 cell components in a single monthly table.&lt;/p&gt;
&lt;p&gt;To reduce the cost of this operation, AirSHIFT virtualized the shift table. The app now only mounts the components within the viewport and unmounts the off-screen components.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/virtualize_before.png&quot; alt=&quot;An annotated screenshot that demonstrates that AirSHIFT used to render
content outside of the viewport.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Before: Rendering all the shift table cells.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/virtualize_after.png&quot; alt=&quot;An annotated screenshot that demonstrates that AirSHIFT now only renders content
that&#39;s visible in the viewport.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
After: Only rendering the cells within the viewport.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In this case, AirSHIFT used &lt;a href=&quot;https://github.com/bvaughn/react-virtualized&quot;&gt;react-virtualized&lt;/a&gt; as there were requirements around enabling complex two dimensional grid tables. They are also exploring ways to convert the implementation to use the lightweight &lt;a href=&quot;https://web.dev/virtualize-long-lists-react-window/&quot;&gt;react-window&lt;/a&gt; in the future.&lt;/p&gt;
&lt;h3 id=&quot;results&quot;&gt;Results &lt;a class=&quot;w-headline-link&quot; href=&quot;#results&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Virtualizing the table alone reduced scripting time by 6 seconds (on a 4x CPU slowdown + Fast 3G throttled Macbook Pro environment). This was the most impactful performance improvement in the refactoring project.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/virtualize_results_before.png&quot; alt=&quot;An annotated screenshot of a Chrome DevTools Performance panel recording.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Before: Around 10 seconds of scripting after user input.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/virtualize_results_after.png&quot; alt=&quot;Another annotated screenshot of a Chrome DevTools Performance panel recording.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
After: 4 seconds of scripting after user input.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;2.-audit-with-user-timing-api&quot;&gt;2. Audit with User Timing API &lt;a class=&quot;w-headline-link&quot; href=&quot;#2.-audit-with-user-timing-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, the AirSHIFT team refactored the scripts that run on user input.
The &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference#main&quot;&gt;flame chart&lt;/a&gt;
of &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/&quot;&gt;Chrome DevTools&lt;/a&gt;
makes it possible to analyze what&#39;s actually happening in the main thread.
But the AirSHIFT team found it easier to analyze application activity based
on React&#39;s lifecycle.&lt;/p&gt;
&lt;p&gt;React 16 provides its performance trace via the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API&quot;&gt;User Timing API&lt;/a&gt;,
which you can visualize from the
&lt;a href=&quot;https://developers.google.com/web/updates/2018/04/devtools#tabs&quot;&gt;Timings section&lt;/a&gt;
of Chrome DevTools. AirSHIFT used the Timings section to find
unnecessary logic running in React lifecycle events.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/user_timing.png&quot; style=&quot;max-width: 75%;&quot; alt=&quot;The Timings section of the Performance panel of Chrome DevTools.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
React&#39;s User Timing events.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Related article: &lt;a href=&quot;https://reactjs.org/docs/optimizing-performance.html#profiling-components-with-the-chrome-performance-tab&quot;&gt;Profiling Components with the Chrome Performance Tab&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;results-2&quot;&gt;Results &lt;a class=&quot;w-headline-link&quot; href=&quot;#results-2&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The AirSHIFT team discovered that an unnecessary
&lt;a href=&quot;https://reactjs.org/docs/reconciliation.html&quot;&gt;React Tree Reconciliation&lt;/a&gt;
was happening right before every route navigation. This meant that
React was updating the shift table unnecessarily before navigations.
An unnecessary Redux state update was causing this issue.
Fixing it saved around 750 ms of scripting time. AirSHIFT
made other micro optimizations as well which eventually led to
a 1 second total reduction in scripting time.&lt;/p&gt;
&lt;h2 id=&quot;3.-lazy-load-components-and-move-expensive-logic-to-web-workers&quot;&gt;3. Lazy load components and move expensive logic to web workers &lt;a class=&quot;w-headline-link&quot; href=&quot;#3.-lazy-load-components-and-move-expensive-logic-to-web-workers&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AirSHIFT has a built-in chat application. Many store owners communicate with their staff members via the chat while looking at the shift table, which means that a user might be typing a message while the table is loading. If the main thread is occupied with scripts that are rendering the table, user input could be janky.&lt;/p&gt;
&lt;p&gt;To improve this experience, AirSHIFT now uses &lt;a href=&quot;https://web.dev/code-splitting-suspense/&quot;&gt;React.lazy and Suspense&lt;/a&gt; to show placeholders for table contents while lazily loading the actual components.&lt;/p&gt;
&lt;p&gt;The AirSHIFT team also migrated some of the expensive business logic
within the lazily loaded components to
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers&quot;&gt;web workers&lt;/a&gt;.
This solved the user input jank problem by freeing up the main thread
so that it could focus on responding to user input.&lt;/p&gt;
&lt;p&gt;Typically developers face complexity in using workers but this time &lt;a href=&quot;https://github.com/GoogleChromeLabs/comlink&quot;&gt;Comlink&lt;/a&gt; did the heavy lifting for them. Below is the pseudo code of how AirSHIFT workerized one of the most expensive operations they had: calculating total labor costs.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In App.js, use React.lazy and Suspense to show fallback content while loading&lt;/em&gt;&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** App.js */&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lazy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Suspense &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Lazily loading the Cost component with React.lazy&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Hello &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./Cost&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Loading&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Some fallback content to show &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; loading&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Showing the fallback content while loading the Cost component by Suspense&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userInfo &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Suspense fallback&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Loading &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Cost &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Suspense&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;&lt;em&gt;In the Cost component, use comlink to execute the calc logic&lt;/em&gt;&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** Cost.js */&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; proxy &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;comlink&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// import the workerlized calc function with comlink&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; WorkerlizedCostCalc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./WorkerlizedCostCalc.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Cost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userInfo &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// execute the calculation in the worker&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WorkerlizedCostCalc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cost&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;&lt;em&gt;Implement the calculation logic that runs in the worker and expose it with comlink&lt;/em&gt;&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// WorkerlizedCostCalc.js&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; expose &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;comlink&#39;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; someExpensiveCalculation &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./CostCalc.js&#39;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Expose the new workerlized calc function with comlink&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;expose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// run existing (expensive) function in the worker&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;someExpensiveCalculation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Related article: &lt;a href=&quot;https://dassur.ma/things/react-redux-comlink/&quot;&gt;React + Redux + Comlink = Off-main-thread&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;results-3&quot;&gt;Results &lt;a class=&quot;w-headline-link&quot; href=&quot;#results-3&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Despite the limited amount of logic they workerized as a trial, AirSHIFT shifted around 100 ms of
their JavaScript from the main thread to the worker thread (simulated with 4x CPU throttling).&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/worker.png&quot; alt=&quot;A screenshot of a Chrome DevTools Performance panel recording that shows that
scripting is now occurring on a web worker rather than the main thread.&quot;&gt;&lt;/p&gt;
&lt;p&gt;AirSHIFT is currently exploring whether they can lazy load other components
and offload more logic to web workers to further reduce jank.&lt;/p&gt;
&lt;h2 id=&quot;4.-setting-a-performance-budget&quot;&gt;4. Setting a performance budget &lt;a class=&quot;w-headline-link&quot; href=&quot;#4.-setting-a-performance-budget&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Having implemented all of these optimizations, it was critical to make sure that the app remains
performant over time. AirSHIFT now uses &lt;a href=&quot;https://github.com/siddharthkp/bundlesize&quot;&gt;bundlesize&lt;/a&gt; to
not exceed the current JavaScript and CSS file size. Aside from setting these basic budgets, they
built a dashboard to show various percentiles of the shift table loading time to check whether the
application is performant even in non-ideal conditions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The script completion time for every Redux event is now measured&lt;/li&gt;
&lt;li&gt;Performance data is collected in &lt;a href=&quot;https://www.elastic.co/jp/&quot;&gt;Elasticsearch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;10th, 25th, 50th, and 75th percentile performance of each event is visualized with &lt;a href=&quot;https://www.elastic.co/jp/products/kibana&quot;&gt;Kibana&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AirSHIFT is now monitoring the shift table loading event to make sure it completes in 3 seconds for
the 75th percentile users. This is an unenforced budget for now but they are considering auto-notifications
via Elasticsearch when they exceed their budget.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/kibana.png&quot; alt=&quot;A chart showing that the 75th percentile completes in around 2500 ms,
the 50th percentile in around 1250 ms, the 25th percentile in around 750 ms,
and the 10th percentile in around 500 ms.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The Kibana dashboard showing daily performance data by percentiles.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Related article: &lt;a href=&quot;https://web.dev/performance-budgets-101&quot;&gt;Performance budgets 101&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;results-4&quot;&gt;Results &lt;a class=&quot;w-headline-link&quot; href=&quot;#results-4&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;From the graph above, you can tell that AirSHIFT is now mostly hitting the 3 seconds budget for 75th percentile users and also loading the shift table within a second for 25th percentile users. By capturing RUM performance data from various conditions and devices, AirSHIFT can now check whether a new feature release is actually affecting the application&#39;s performance or not.&lt;/p&gt;
&lt;h2 id=&quot;5.-performance-hackathons&quot;&gt;5. Performance hackathons &lt;a class=&quot;w-headline-link&quot; href=&quot;#5.-performance-hackathons&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even though all of these performance optimization efforts were important and impactful,
it&#39;s not always easy to get engineering and business teams to prioritize non-functional
development. Part of the challenge is that some of these performance optimizations
can&#39;t be planned. They require experimentation and a trial-and-error mindset.&lt;/p&gt;
&lt;p&gt;AirSHIFT is now conducting internal 1-day performance hackathons to let engineers focus only on performance related work. In these hackathons they remove all constraints and respect the engineers&#39; creativity, meaning any implementation that contributes to speed is worth considering. To accelerate the hackathon, AirSHIFT splits the group into small teams and each team competes to see who can get the biggest &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; performance score improvement.
The teams get very competitive! 🔥&lt;/p&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/hackathon.png&quot; alt=&quot;Photos of the hackathon.&quot;&gt;
&lt;h3 id=&quot;results-5&quot;&gt;Results &lt;a class=&quot;w-headline-link&quot; href=&quot;#results-5&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The hackathon approach is working well for them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Performance bottlenecks can be easily detected by actually trying out multiple approaches during the hackathon and measuring each with Lighthouse.&lt;/li&gt;
&lt;li&gt;After the hackathon, it&#39;s rather easy to convince the team which optimization they should be prioritizing for production release.&lt;/li&gt;
&lt;li&gt;It&#39;s also an effective way of advocating the importance of speed. Every participant can understand the correlation between how you code and how it results in performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good side effect was that many other engineering teams within Recruit got interested in this hands-on approach and the AirSHIFT team is now facilitating multiple speed hackathons within the company.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;w-headline-link&quot; href=&quot;#summary&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It was definitely not the easiest journey for AirSHIFT to work on these optimizations but it certainly paid off. Now AirSHIFT is loading the shift table within 1.5 sec in median which is a 6x improvement from their performance
before the project.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/compare_speed_vp9.webm&quot; type=&quot;video/webm; codecs=vp8&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/compare_speed_h264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;After the performance optimizations launched, one user said:&lt;/p&gt;
&lt;blockquote class=&quot;w-blockquote&quot;&gt;
Thank you so much for making the shift table load fast.
Arranging the shift work is so much more efficient now.
&lt;/blockquote&gt;
</content>
<author>
<name>Yusuke Utsunomiya</name>
</author><author>
<name>Yosuke Furukawa</name>
</author><author>
<name>Satoshi Arai</name>
</author><author>
<name>Kento Tsuji</name>
</author>
</entry>
<entry>
<title>Virtual reality comes to the web</title>
<link href="https://web.dev/vr-comes-to-the-web/"/>
<updated>2019-10-30T17:00:00-07:00</updated>
<id>https://web.dev/vr-comes-to-the-web/</id>
<content type="html">&lt;p&gt;Immersive experiences have come to the web in Chrome 79. The WebXR Device API
brings virtual reality (with augmented reality to come later). While an update
to the GamePad API extends the advanced use of controlls to VR. Other browsers
will be supporting these specs soon, including Firefox Reality, Oculus Browser,
Edge and Magic Leap&#39;s Helio browser, among others.&lt;/p&gt;
&lt;p&gt;This article begins a series on the immersive web. This installment covers
setting up a basic WebXR application and constructing a frame loop, the
workhorse of an immersive experience. Later articles will cover drawing to a
screen and interacting with input devices using &lt;a href=&quot;https://www.chromestatus.com/features/5659025263820800&quot;&gt;Gamepad API
updates&lt;/a&gt; mentioned
above. Though Chrome only supports virtual reality for now, everything I cover
in this and succeeding articles applies equally to both AR and VR.&lt;/p&gt;
&lt;p&gt;If you&#39;ve followed the progress of immersive experiences on the web, you know
there has been much experimentation in recent years. An early WebVR
implementation, released in 2017 in Firefox and in 2018 in Chrome behind a flag,
proved inadequate for &lt;a href=&quot;https://developers.google.com/web/updates/2018/05/welcome-to-immersive#what_happened_to_webvr_11&quot;&gt;various
reasons&lt;/a&gt;.
The work to correct its shortcomings blossomed last year into the &lt;a href=&quot;https://www.w3.org/TR/webxr/&quot;&gt;WebXR Device
API&lt;/a&gt;. It was in an origin trial for a while, and
now Chrome is enabling it by default.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-immersive-web&quot;&gt;What is the immersive web? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-is-the-immersive-web&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Though we use two terms to describe immersive experiences—augmented
reality and virtual reality—many think of them on a spectrum from complete
reality to completely virtual, with degrees of immersion in between. The &#39;X&#39; in
XR is intended to reflect that thinking by being a sort of algebraic variable
that stands for anything in the spectrum of immersive experiences.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/vr-comes-to-the-web/immersive-spectrum.png&quot; style=&quot;max-width: 100%;&quot; alt=&quot;A graph illustrating the spectrum of visual experiences from complete reality to completely immersive.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
The spectrum of immersive experiences
&lt;/figcaption&gt;
&lt;/figure&gt;Examples of immersive experiences include:&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Games&lt;/li&gt;
&lt;li&gt;360° videos&lt;/li&gt;
&lt;li&gt;Traditional 2D (or 3D) videos presented in immersive surroundings&lt;/li&gt;
&lt;li&gt;Home buying&lt;/li&gt;
&lt;li&gt;Viewing products in your home before you buy them&lt;/li&gt;
&lt;li&gt;Immersive art&lt;/li&gt;
&lt;li&gt;Something cool nobody&#39;s thought of yet&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;concepts-and-usage&quot;&gt;Concepts and usage &lt;a class=&quot;w-headline-link&quot; href=&quot;#concepts-and-usage&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ll explain a few basics of using the WebXR Device API. If you need more depth
than I&#39;ve provided, check out the Immersive Web Working Group&#39;s &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/&quot;&gt;WebXR
samples&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API&quot;&gt;MDN&#39;s growing
reference
materials&lt;/a&gt;.
If you&#39;re familiar with early versions of the WebXR Device API, you should
glance over all of this material. There have been changes.&lt;/p&gt;
&lt;p&gt;The code in this article is based on the Immersive Web Working Group&#39;s barebones
sample (&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/xr-barebones.html&quot;&gt;demo&lt;/a&gt;,
&lt;a href=&quot;https://github.com/immersive-web/webxr-samples/blob/master/xr-barebones.html&quot;&gt;source&lt;/a&gt;),
but is edited for clarity and simplicity.&lt;/p&gt;
&lt;p&gt;Part of creating the WebXR specification has been fleshing out security and
privacy measures to protect users. Consequently, implementations must adhere to
certain requirements. A web page or app must be active and focused before it can
request anything sensitive from the viewer. Web pages or apps must be served
over HTTPS. The API itself is designed to protect information obtained from
sensors and cameras, which it needs in order to function.&lt;/p&gt;
&lt;h3 id=&quot;requesting-a-session&quot;&gt;Requesting a session &lt;a class=&quot;w-headline-link&quot; href=&quot;#requesting-a-session&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Entering an XR session requires a user gesture. To get that, use feature
detection and make a call to &lt;code&gt;isSessionSupported()&lt;/code&gt;. In the example below, I&#39;ve
indicated that I want a virtual reality session with the &lt;code&gt;&#39;immersive-vr&#39;&lt;/code&gt;
session type. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XR/isSessionSupported#Syntax&quot;&gt;Other session
types&lt;/a&gt;
are defined in the spec and will be available in future versions of Chrome. Once
I know that virtual reality sessions are supported, I enable a button that lets
me acquire a user gesture.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supported &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isSessionSupported&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;immersive-vr&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;supported&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onButtonClicked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Enter VR&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;After enabling the button, I wait for a click event then request a session.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onButtonClicked&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;xrSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;immersive-vr&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Exit XR&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Notice the object hierarchy in this code. It moves from &lt;code&gt;navigator&lt;/code&gt; to &lt;code&gt;xr&lt;/code&gt; to
an &lt;code&gt;XRSession&lt;/code&gt; instance. In early versions of the API, a script had to request a
device before requesting a session. Now, the device is acquired implicitly.&lt;/p&gt;
&lt;h3 id=&quot;entering-a-session&quot;&gt;Entering a session &lt;a class=&quot;w-headline-link&quot; href=&quot;#entering-a-session&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After getting a session, I need to start it and enter it. But first, I need to
set up a few things. A session needs an &lt;code&gt;onend&lt;/code&gt; event handler so that the app or
web page can be reset when the user exits.&lt;/p&gt;
&lt;p&gt;I&#39;ll also need a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element to draw my scene on. It needs to be an
XR-compatible WebGL context. All drawing is done using the WebGL API or a
WebGL-based framework such as &lt;a href=&quot;https://threejs.org/&quot;&gt;Three.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now that I have a place to draw, I need a source of content to draw on
it. For that, I create an instance of &lt;code&gt;XRWebGLLayer&lt;/code&gt;. I associate it with the
canvas by calling &lt;code&gt;XRSession.updateRenderState()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once I&#39;m in a session, I need a way of determining where things are in virtual
reality. I&#39;ll need a reference space. A &lt;code&gt;&#39;local-floor&#39;&lt;/code&gt; reference space is one
where the origin is located near the viewer and the y-axis is 0 at floor level
and is not expected to move. There are &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XRSession/requestReferenceSpace&quot;&gt;other types of reference
spaces&lt;/a&gt;,
but that is a more complicated topic than I can go into here. I save the
reference space to a variable because I&#39;ll need it when I draw to the screen.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; canvas &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; gl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webgl&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; xrCompatible&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateRenderState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; baseLayer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestReferenceSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;local-floor&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;refSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrRefSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; refSpace&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;After getting a reference space, I call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt;.
This is the start of presenting virtual content, which is done in the frame
loop.&lt;/p&gt;
&lt;h3 id=&quot;running-a-frame-loop&quot;&gt;Running a frame loop &lt;a class=&quot;w-headline-link&quot; href=&quot;#running-a-frame-loop&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The frame loop is a user-agent controlled infinite loop in which content is
repeatedly drawn to the screen. Content is drawn in discrete blocks called
frames. The succession of frames creates the illusion of movement. The number of
frames per second currently varies between devices, and may be anything from 60
to 144, but that has no bearing on your code.&lt;/p&gt;
&lt;p&gt;The basic process for the frame loop is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt;. In response, the user agent invokes the &lt;code&gt;XRFrameRequestCallback&lt;/code&gt;, which is defined by you.&lt;/li&gt;
&lt;li&gt;Inside your callback function:
&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt; again.&lt;/li&gt;
&lt;li&gt;Query for the position (called a pose in WebXR) of the viewer.&lt;/li&gt;
&lt;li&gt;Draw content from the viewer&#39;s point of view.&lt;/li&gt;
&lt;li&gt;Process user input.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this section I&#39;ll mainly cover steps 2-1 and 2-2. I&#39;ll cover the remaining
steps in later articles.&lt;/p&gt;
&lt;h4 id=&quot;the-xrframerequestcallback&quot;&gt;The XRFrameRequestCallback &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-xrframerequestcallback&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;XRFrameRequestCallback&lt;/code&gt; is defined by you. It takes two parameters: a
&lt;code&gt;DOMHighResTimeStamp&lt;/code&gt; and an &lt;code&gt;XRFrame&lt;/code&gt; instance. The &lt;code&gt;XRFrame&lt;/code&gt; object provides
the information needed to render a single frame to the display. The
&lt;code&gt;DOMHighResTimeStamp&lt;/code&gt; argument is for future use.&lt;/p&gt;
&lt;p&gt;Before doing anything else, I&#39;m going to request the next animation frame. As
previously stated, the timing of frames is determined by the user agent based on
the underlying hardware. Requesting the next frame first ensures that if
something during the callback throws an error I can ensure that the frame loop
continues.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Render a frame.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h4 id=&quot;getting-poses&quot;&gt;Getting poses &lt;a class=&quot;w-headline-link&quot; href=&quot;#getting-poses&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Before drawing anything on the screen, I need to know where the viewer is in
immersive space. The position and orientation of a thing in immersive space is
called a pose. Both viewers and input devices have a pose. Both viewer and input
device poses are defined as an &lt;code&gt;XRRigidTransform&lt;/code&gt;, which consists of a position
vector and an orientation quaternion. I get the viewer&#39;s pose by calling
&lt;code&gt;XRFrame.getViewerPose()&lt;/code&gt; on the current animation frame. I pass it the
reference space I acquired when I set up the session.&lt;/p&gt;
&lt;p&gt;Next, I test whether an &lt;code&gt;XRViewerPose&lt;/code&gt; was returned because if something went
wrong, I can&#39;t render the frame. But as stated earlier, I already called
&lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt; so that if the system can recover, the
frame loop will continue. If not, it will end the session and call the &lt;code&gt;end&lt;/code&gt;
event handler.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrViewerPose&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Render based on the pose.&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h4 id=&quot;drawing-the-views&quot;&gt;Drawing the views &lt;a class=&quot;w-headline-link&quot; href=&quot;#drawing-the-views&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;After checking the pose, it&#39;s time to draw something. The &lt;code&gt;XRViewerPose&lt;/code&gt;
contains an array of &lt;code&gt;XRView&lt;/code&gt; interfaces which represents a display or a portion
of a display and returns the information needed to render content that&#39;s
correctly positioned for the device and the viewer such as the field of view, eye
offset, and other optical properties. Since I&#39;m drawing for two eyes, I have two
views, which I loop through and draw a separate image for each.&lt;/p&gt;
&lt;p&gt;If I were implementing for phone-based augmented reality, I would have only one
view but I&#39;d still loop through them. This is an important difference between
WebXR and other immersive systems. Though it may seem pointless to iterate
through one view, doing so allows you to have a single rendering path for a
spectrum of immersive experiences.&lt;/p&gt;
&lt;p&gt;One thing I didn&#39;t cover is how to draw to the screen, though I&#39;ve shown it
below. That&#39;s done through layer objects such as the &lt;code&gt;XRWebGLLayer&lt;/code&gt; interface and a
means of drawing graphics such as the WebGL APIs or the Three.js framework. It&#39;s
such a lengthy subject it will be covered in a later article.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrViewerPose&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; glLayer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;renderState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;baseLayer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Bind the baseLayer’s framebuffer and use WebGL to draw something.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; gl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindFramebuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FRAMEBUFFER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; glLayer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;framebuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrView &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; xrViewerPose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;views&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; vp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; glLayer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;view&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; gl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;viewport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; vp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; vp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; vp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Draw to the portion of the framebuffer associated with this view.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;end-the-session&quot;&gt;End the session &lt;a class=&quot;w-headline-link&quot; href=&quot;#end-the-session&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An immersive session may end for several reasons including ending by your own
code through a call to &lt;code&gt;XRSession.end()&lt;/code&gt;. Other causes include the headset being
disconnected or another application taking control of it. This is why a
well-behaved application should monitor the &lt;code&gt;end&lt;/code&gt; event. When it occurs, discard
the session and its related render objects. An ended immersive session cannot be
resumed. To reenter the immersive experience, my app needs to start a new
session.&lt;/p&gt;
&lt;p&gt;Recall from &lt;a href=&quot;#entering-a-session&quot;&gt;Entering a session&lt;/a&gt; that during setup, I added
an &lt;code&gt;onend&lt;/code&gt; event handler.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// More setup…&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Inside the event handler, restore the state of the app before the user entered a
session.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionEnded&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Enter VR&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that I&#39;ve shown you how the frame loop works, you should have enough to at
least make sense of sample code. Hopefully that&#39;s enough to start experimenting.
In the next article, I&#39;ll cover a bit about WebGL and how it interacts with
certain WebXR interfaces.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@jeshoots&quot;&gt;JESHOOTS.COM&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
<author>
<name>Joe Medley</name>
</author>
</entry>
<entry>
<title>SameSite cookie recipes</title>
<link href="https://web.dev/samesite-cookie-recipes/"/>
<updated>2019-10-29T17:00:00-07:00</updated>
<id>https://web.dev/samesite-cookie-recipes/</id>
<content type="html">&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;For how cookies and &lt;code&gt;SameSite&lt;/code&gt; work, see part 1:
&lt;a href=&quot;https://web.dev/samesite-cookies-explained&quot;&gt;SameSite cookies explained&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://www.chromium.org/updates/same-site&quot;&gt;Chrome&lt;/a&gt;,
&lt;a href=&quot;https://groups.google.com/d/msg/mozilla.dev.platform/nx2uP0CzA9k/BNVPWDHsAQAJ&quot;&gt;Firefox&lt;/a&gt;,
&lt;a href=&quot;https://groups.google.com/a/chromium.org/d/msg/blink-dev/AknSSyQTGYs/8lMmI5DwEAAJ&quot;&gt;Edge&lt;/a&gt;,
and others will be changing their default behavior in line with the IETF
proposal,
&lt;a href=&quot;https://tools.ietf.org/html/draft-west-cookie-incrementalism-00&quot;&gt;Incrementally Better Cookies&lt;/a&gt;
so that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cookies without a &lt;code&gt;SameSite&lt;/code&gt; attribute will be treated as &lt;code&gt;SameSite=Lax&lt;/code&gt;,
meaning the default behavior will be to restrict cookies to first party
contexts &lt;strong&gt;only&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cookies for cross-site usage &lt;strong&gt;must&lt;/strong&gt; specify &lt;code&gt;SameSite=None; Secure&lt;/code&gt; to
enable inclusion in third party context.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This will become the
&lt;a href=&quot;https://blog.chromium.org/2019/10/developers-get-ready-for-new.html&quot;&gt;default behavior in Chrome 80&lt;/a&gt;,
planned for a stable release in February 2020. If you currently provide cookies
that are intended for cross-site usage you will need to make changes before that
date to support the new default.&lt;/p&gt;
&lt;h2 id=&quot;use-cases-for-cross-site-or-third-party-cookies&quot;&gt;Use cases for cross-site or third-party cookies &lt;a class=&quot;w-headline-link&quot; href=&quot;#use-cases-for-cross-site-or-third-party-cookies&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a number of common use cases and patterns where cookies need to be
sent in a third-party context. If you provide or depend on one of these use
cases, ensure that either you or the provider are updating their cookies to
ensure the service continues to function correctly.&lt;/p&gt;
&lt;h3 id=&quot;content-within-an-lessiframegreater&quot;&gt;Content within an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#content-within-an-lessiframegreater&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Content from a different site displayed in an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; is in a third-party
context. Standard use cases here are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Embedded content shared from other sites, such as videos, maps, code samples,
and social posts.&lt;/li&gt;
&lt;li&gt;Widgets from external services such as payments, calendars, booking, and
reservation functionality.&lt;/li&gt;
&lt;li&gt;Widgets such as social buttons or anti-fraud services that create less obvious
&lt;code&gt;&amp;lt;iframes&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cookies may be used here to, among other things, maintain session state, store
general preferences, enable statistics, or personalize content for users with
existing accounts.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/samesite-cookie-recipes/iframe.png&quot; alt=&quot;Diagram of a browser window where the URL of embedded content does
not match the URL of the page.&quot; style=&quot;max-width: 35vw;&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
If the embedded content doesn&#39;t come from the same site as the top-level
browsing context, it&#39;s third-party content.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Additionally, as the web is inherently composable, &lt;code&gt;&amp;lt;iframes&amp;gt;&lt;/code&gt; are used to embed
content that is also viewed in a top-level or first-party context. Any cookies
used by that site will be considered as third-party cookies when the site is
displayed within the frame. If you&#39;re creating sites that you intend to be
easily embedded by others while also relying on cookies to function, you will
also need to ensure those are marked for cross-site usage or that you can
gracefully fallback without them.&lt;/p&gt;
&lt;h3 id=&quot;&amp;quot;unsafe&amp;quot;-requests-across-sites&quot;&gt;&amp;quot;Unsafe&amp;quot; requests across sites &lt;a class=&quot;w-headline-link&quot; href=&quot;#&amp;quot;unsafe&amp;quot;-requests-across-sites&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While &amp;quot;unsafe&amp;quot; may sound slightly concerning here, this refers to any request
that may be intended to change state. On the web that&#39;s primarily POST requests.
Cookies marked as &lt;code&gt;SameSite=Lax&lt;/code&gt; will be sent on safe top-level navigations,
e.g. clicking a link to go to a different site. However something like a
&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; submission via POST to a different site would not include cookies.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/samesite-cookie-recipes/safe-navigation.png&quot; alt=&quot;Diagram of a request moving from one page to another.&quot; style=&quot;max-width: 35vw;&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
If the incoming request uses a &quot;safe&quot; method then the cookies will be sent.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This pattern is used for sites that may redirect the user out to a remote
service to perform some operation before returning, for example redirecting to a
third-party identity provider. Before the user leaves the site, a cookie is set
containing a single use token with the expectation that this token can be
checked on the returning request to mitigate
&lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html&quot;&gt;Cross Site Request Forgery (CSRF)&lt;/a&gt;
attacks. If that returning request comes via POST then it will be necessary to
mark the cookies as &lt;code&gt;SameSite=None; Secure&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;remote-resources&quot;&gt;Remote resources &lt;a class=&quot;w-headline-link&quot; href=&quot;#remote-resources&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Any remote resource on a page may be relying on cookies to be sent with a
request, from &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags, &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags, and so on. Common use cases include
tracking pixels and personalizing content.&lt;/p&gt;
&lt;p&gt;This also applies to requests initiated from your JavaScript by &lt;code&gt;fetch&lt;/code&gt; or
&lt;code&gt;XMLHttpRequest&lt;/code&gt;. If &lt;code&gt;fetch()&lt;/code&gt; is called with the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included&quot;&gt;&lt;code&gt;credentials: &#39;include&#39;&lt;/code&gt; option&lt;/a&gt;
this is a good indication that cookies may well be expected on those requests.
For &lt;code&gt;XMLHttpRequest&lt;/code&gt; you should look for instances of the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials&quot;&gt;&lt;code&gt;withCredentials&lt;/code&gt; property&lt;/a&gt;
being set to &lt;code&gt;true&lt;/code&gt;. This is a good indication that cookies may well be expected
on those requests. Those cookies will need to be appropriately marked to be
included in cross-site requests.&lt;/p&gt;
&lt;h3 id=&quot;content-within-a-webview&quot;&gt;Content within a WebView &lt;a class=&quot;w-headline-link&quot; href=&quot;#content-within-a-webview&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A WebView in a native app is powered by a browser and you will need to test if
the same restrictions or issues apply. In Android, if the WebView is powered by
Chrome the new defaults &lt;strong&gt;will not&lt;/strong&gt; immediately be applied with Chrome 80.
However the intent is to apply them in the future, so you should still test and
prepare for this. Additionally, Android allows native apps to set cookies
directly via the
&lt;a href=&quot;https://developer.android.com/reference/android/webkit/CookieManager&quot;&gt;CookieManager API&lt;/a&gt;.
As with cookies set via headers or JavaScript, consider including
&lt;code&gt;SameSite=None; Secure&lt;/code&gt; if they are intended for cross-site use.&lt;/p&gt;
&lt;h2 id=&quot;how-to-implement-samesite-today&quot;&gt;How to implement &lt;code&gt;SameSite&lt;/code&gt; today &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-to-implement-samesite-today&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For cookies where they are only needed in a first-party context you should
ideally mark them as &lt;code&gt;SameSite=Lax&lt;/code&gt; or &lt;code&gt;SameSite=Strict&lt;/code&gt; depending on your
needs. You can also choose to do nothing and just allow the browser to enforce
its default, but this comes with the risk of inconsistent behavior across
browsers and potential console warnings for each cookie.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;Set-Cookie: first_party_var=value; SameSite=Lax&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;For cookies needed in a third-party context, you will need to ensure they are
marked as &lt;code&gt;SameSite=None; Secure&lt;/code&gt;. Note that you need both attributes together.
If you just specify &lt;code&gt;None&lt;/code&gt; without &lt;code&gt;Secure&lt;/code&gt; the cookie will be rejected. There
are some mutually incompatible differences in browser implementations though, so
you may need to use some of the mitigating strategies described in
&lt;a href=&quot;#handling-incompatible-clients&quot;&gt;Handling incompatible clients&lt;/a&gt; below.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;Set-Cookie: third_party_var=value; SameSite=None; Secure&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;identifying-cookie-usage&quot;&gt;Identifying cookie usage &lt;a class=&quot;w-headline-link&quot; href=&quot;#identifying-cookie-usage&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As of Chrome 77, you will see warnings in the DevTools Console for cross-site
cookies that do not currently have a &lt;code&gt;SameSite&lt;/code&gt; attribute and cookies that have
been marked with &lt;code&gt;SameSite=None&lt;/code&gt; but are missing &lt;code&gt;Secure&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/samesite-cookie-recipes/chrome-console-warning.png&quot; alt=&quot;Chrome DevTools Console warnings for SameSite cookie misconfiguration.&quot; style=&quot;max-width: 40vw;&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;For missing &lt;code&gt;SameSite&lt;/code&gt; attributes you will see:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;A cookie associated with a cross-site resource at (cookie domain)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;was set without the `SameSite` attribute. A future release of Chrome&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;will only deliver cookies with cross-site requests if they are set&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;with `SameSite=None` and `Secure`. You can review cookies in developer&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;tools under Application&gt;Storage&gt;Cookies and see more details at&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;https://www.chromestatus.com/feature/5088147346030592 and&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;https://www.chromestatus.com/feature/5633521622188032.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;And for &lt;code&gt;None&lt;/code&gt; without &lt;code&gt;Secure&lt;/code&gt;, you will see:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;A cookie associated with a resource at (cookie domain) was set with&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;`SameSite=None` but without `Secure`. A future release of Chrome will only&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;deliver cookies marked `SameSite=None` if they are also marked `Secure`. You&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;can review cookies in developer tools under Application&gt;Storage&gt;Cookies and&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;see more details at https://www.chromestatus.com/feature/5633521622188032.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Each of these warnings will contain the cookie domain. If you&#39;re responsible for
that domain, then you will need to update the cookies. Otherwise, you may need
to contact the owner of the site or service responsible for that cookie to
ensure they&#39;re making the necessary changes. The warnings themselves do not
affect the functionality of the site, this is purely to inform developers of the
upcoming changes.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/samesite-cookie-recipes/samesite-devtools.png&quot; alt=&quot;The Cookies pane of Chrome DevTools.&quot; style=&quot;max-width: 40vw;&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The &lt;a href=&quot;https://devtools.chrome.com/storage/cookies&quot;&gt;Cookies pane&lt;/a&gt;.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;handling-incompatible-clients&quot;&gt;Handling incompatible clients &lt;a class=&quot;w-headline-link&quot; href=&quot;#handling-incompatible-clients&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As these changes to include &lt;code&gt;None&lt;/code&gt; and update default behavior are still
relatively new, there are inconsistencies amongst browsers as to how these
changes are handled. You can refer to the
&lt;a href=&quot;https://www.chromium.org/updates/same-site/incompatible-clients&quot;&gt;updates page on chromium.org&lt;/a&gt;
for the issues currently known, however it&#39;s not possible to say if this is
exhaustive. While this is not ideal, there are workarounds you can employ during
this transitionary phase. The general rule though is to treat incompatible
clients as the special case. Do not create an exception for browsers
implementing the newer rules.&lt;/p&gt;
&lt;p&gt;The first option is to set both the new and old style cookies:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;Set-cookie: 3pcookie=value; SameSite=None; Secure&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;Set-cookie: 3pcookie-legacy=value; Secure&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Browsers implementing the newer behavior will set the cookie with the &lt;code&gt;SameSite&lt;/code&gt;
value, while other browsers may ignore or incorrectly set it. However, those
same browsers will set the &lt;code&gt;3pcookie-legacy&lt;/code&gt; cookie. When processing included
cookies, the site should first check for the presence of the new style cookie
and if it&#39;s not found, then fallback to the legacy cookie.&lt;/p&gt;
&lt;p&gt;The example below shows how to do this in Node.js, making use of the
&lt;a href=&quot;https://expressjs.com/&quot;&gt;Express framework&lt;/a&gt; and its
&lt;a href=&quot;https://www.npmjs.com/package/cookie-parser&quot;&gt;cookie-parser&lt;/a&gt; middleware.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; express &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;express&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cookie-parser&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/set&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Set the new style cookie&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cookie&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;3pcookie&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sameSite&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;none&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; secure&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// And set the same value in the legacy cookie&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cookie&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;3pcookie-legacy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secure&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; cookieVal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;3pcookie&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// check the new style cookie first&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; cookieVal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;3pcookie&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;3pcookie-legacy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// otherwise fall back to the legacy cookie&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; cookieVal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;3pcookie-legacy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The downside is that this involves setting redundant cookies to cover all
browsers and requires making changes both at the point of setting and reading
the cookie. However, this approach should cover all browsers regardless of their
behavior and ensure third-party cookies continue to function as before.&lt;/p&gt;
&lt;p&gt;Alternatively at the point of sending the &lt;code&gt;Set-Cookie&lt;/code&gt; header, you can choose to
detect the client via the user agent string. Refer to the
&lt;a href=&quot;https://www.chromium.org/updates/same-site/incompatible-clients&quot;&gt;list of incompatible clients&lt;/a&gt;
and then make use of an appropriate library for your platform, for example
&lt;a href=&quot;https://www.npmjs.com/package/ua-parser-js&quot;&gt;ua-parser-js&lt;/a&gt; library on Node.js.
It&#39;s advisable to find a library to handle user agent detection as you most
probably don&#39;t want to write those regular expressions yourself.&lt;/p&gt;
&lt;p&gt;The benefit of this approach is that it only requires making one change at the
point of setting the cookie. However, the necessary warning here is that user
agent sniffing is inherently fragile and may not catch all of the affected
users.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Regardless of what option you choose, it&#39;s advisable to ensure you have a way of
logging the levels of traffic that are going through the legacy route. Make sure
you have a reminder or alert to remove this workaround once those levels drop
below an acceptable threshold for your site.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;support-for-samesitenone-in-languages-libraries-and-frameworks&quot;&gt;Support for &lt;code&gt;SameSite=None&lt;/code&gt; in languages, libraries, and frameworks &lt;a class=&quot;w-headline-link&quot; href=&quot;#support-for-samesitenone-in-languages-libraries-and-frameworks&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The majority of languages and libraries support the &lt;code&gt;SameSite&lt;/code&gt; attribute for
cookies, however the addition of &lt;code&gt;SameSite=None&lt;/code&gt; is still relatively new which
means that you may need to work around some of the standard behavior for now.
These are documented in the
&lt;a href=&quot;https://github.com/GoogleChromeLabs/samesite-examples&quot;&gt;&lt;code&gt;SameSite&lt;/code&gt; examples repo on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;getting-help&quot;&gt;Getting help &lt;a class=&quot;w-headline-link&quot; href=&quot;#getting-help&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cookies are all over the place and it&#39;s rare for any site to have completely
audited where they&#39;re set and used, especially once you throw cross-site use
cases in the mix. When you encounter an issue, it may well be the first time
anyone has encountered it - so don&#39;t hesitate to reach out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raise an issue on the
&lt;a href=&quot;https://github.com/GoogleChromeLabs/samesite-examples&quot;&gt;&lt;code&gt;SameSite&lt;/code&gt; examples repo on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Post a question on the
&lt;a href=&quot;https://stackoverflow.com/questions/tagged/samesite&quot;&gt;&amp;quot;samesite&amp;quot; tag on StackOverflow&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For issues with Chromium&#39;s behavior, raise a bug via the
&lt;a href=&quot;https://bit.ly/2lJMd5c&quot;&gt;[SameSite cookies] issue template&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Follow Chrome&#39;s progress on the
&lt;a href=&quot;https://www.chromium.org/updates/same-site&quot;&gt;&lt;code&gt;SameSite&lt;/code&gt; updates page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Cookie hero image by
&lt;a href=&quot;https://unsplash.com/@calya1?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Cayla1&lt;/a&gt;
on
&lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
<author>
<name>Rowan Merewood</name>
</author>
</entry>
<entry>
<title>Fast ads matter</title>
<link href="https://web.dev/fast-ads-matter/"/>
<updated>2019-10-28T17:00:00-07:00</updated>
<id>https://web.dev/fast-ads-matter/</id>
<content type="html">&lt;p&gt;If you&#39;re like most publishers on the web, your business offers a simple value
exchange: you provide content that users find valuable, and in the process
present them with relevant ads to generate revenue. But if those ads slow down
the content, are you really upholding your end of the bargain?&lt;/p&gt;
&lt;p&gt;This post explains how fast ads benefit everyone, and how to start investigating
and improving ad speed on your sites.&lt;/p&gt;
&lt;h2 id=&quot;why-do-fast-ads-matter&quot;&gt;Why do fast ads matter? &lt;a class=&quot;w-headline-link&quot; href=&quot;#why-do-fast-ads-matter&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;fast-ads-improve-the-user-experience&quot;&gt;Fast ads improve the user experience &lt;a class=&quot;w-headline-link&quot; href=&quot;#fast-ads-improve-the-user-experience&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Users come to your site to be entertained, get informed, or learn something new,
and they expect your site to load quickly, with minimal interruption. If your
site helps users do that well, they tend to return more often. While ads may be
necessary for your business, if they&#39;re slowing down your site they can create a
tension with the user&#39;s purpose.&lt;/p&gt;
&lt;p&gt;The browser has limited resources to work with—memory, CPU, and network
bandwidth. The more of these resources your ads consume, the longer it takes for
your page to become visually complete and &lt;a href=&quot;https://web.dev/interactive/&quot;&gt;interactive&lt;/a&gt;. This can
be a drag on user experience metrics like &lt;a href=&quot;https://en.wikipedia.org/wiki/Session_(web_analytics)&quot;&gt;session
length&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Bounce_rate&quot;&gt;bounce
rate&lt;/a&gt;. You can improve these metrics
by serving the most lightweight ads possible and loading them at the right time
(which is not always right away).&lt;/p&gt;
&lt;p&gt;For many e-commerce publishers, display ads are a secondary source of revenue.
If you&#39;re one of these publishers, you know that any ads you place on the page
have some negative impact on your primary business metrics (sales,
subscriptions, and more). Fast ads, by getting out of the page&#39;s way, give your
primary business metrics a boost as well.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;When asked about their reasons for installing ad blockers, &lt;a href=&quot;https://pagefair.com/blog/2017/adblockreport/&quot;&gt;many
users cited &amp;quot;interruption&amp;quot; and
&amp;quot;speed&amp;quot;&lt;/a&gt; as primary motivators.
Since fast ads result in improved user experience metrics, a focus on improving
ad speed may decrease the incentive for users to install ad blockers.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;fast-ads-make-you-more-money&quot;&gt;Fast ads make you more money &lt;a class=&quot;w-headline-link&quot; href=&quot;#fast-ads-make-you-more-money&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another way to think about this topic is from an advertiser&#39;s point of view. The
sooner an ad appears on the page, the longer it will be visible on the screen,
meaning it&#39;s more likely to be seen and interacted with. As views and
interactions increase, so does the value of your ad slots in the eyes of
advertisers.&lt;/p&gt;
&lt;p&gt;Conversely,
&lt;a href=&quot;https://en.wikipedia.org/wiki/Impression_(online_media)&quot;&gt;impressions&lt;/a&gt; and
&lt;a href=&quot;https://en.wikipedia.org/wiki/Viewable_Impression&quot;&gt;viewable impressions&lt;/a&gt;
decrease the longer an ad takes to appear on the page. To provide a sense of the
magnitude of this problem, the charts below show aggregated data from an
experiment where a delay between 100 ms and 1 s was injected before
each ad response, across 4 billion impressions on websites with the Google
Publisher Tag in multi-request mode. The dotted lines are extrapolations to
visualize how improving ad speed could increase impressions and viewability
rate.&lt;/p&gt;
&lt;p&gt;With 1 s of added delay, impressions decreased by 1.1% for mobile traffic
and 1.9% for desktop traffic:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/fast-ads-matter/ad-latency-injected-vs-impressions-change.svg&quot; alt=&quot;Chart showing latency injected vs. impressions change&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Source: Google Internal Data, December 2016 to January 2017.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With 1 s of added delay, viewability rate decreased by 3.6% for mobile
traffic and 2.9% for desktop traffic:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/fast-ads-matter/ad-latency-injected-vs-viewability-rate-change.svg&quot; alt=&quot;Chart showing latency injected vs. viewability rate change&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Source: Google Internal Data, December 2016 to January 2017.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;a-framework-for-thinking-about-ad-speed&quot;&gt;A framework for thinking about ad speed &lt;a class=&quot;w-headline-link&quot; href=&quot;#a-framework-for-thinking-about-ad-speed&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Modern websites tend to have complex and diverse ad serving setups, which means
there&#39;s no one-size-fits-all method of making ads fast. Instead, the following
sections give you a framework for thinking about ad speed. Some points are
specific to Google Ad Manager, but the principles apply even if you&#39;re using a
different ad server.&lt;/p&gt;
&lt;h3 id=&quot;know-why-you-want-to-improve-ad-speed&quot;&gt;Know why you want to improve ad speed &lt;a class=&quot;w-headline-link&quot; href=&quot;#know-why-you-want-to-improve-ad-speed&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before you start working to improve ad speed, you should be clear on what your
goals are. Is it to improve the user experience? To increase viewability? Both?&lt;/p&gt;
&lt;p&gt;Whatever your specific goals are, it&#39;s important to identify the metrics you can
use to measure and track progress towards them over time. Having the right
metrics in place allows you to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Know if the changes you&#39;re making are moving you in the right direction.&lt;/li&gt;
&lt;li&gt;Run experiments, such as A/B tests, to evaluate the effectiveness of
specific changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you&#39;ve decided on the metrics that make sense for you, be sure to configure
reporting so you can easily keep track of them. A dashboard you can check
periodically or scheduled reports sent to you by email work well for that.&lt;/p&gt;
&lt;h3 id=&quot;know-your-inventory-and-dependencies&quot;&gt;Know your inventory and dependencies &lt;a class=&quot;w-headline-link&quot; href=&quot;#know-your-inventory-and-dependencies&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To identify opportunities for improving ad speed, you first need to understand
the types of inventory your site supports and the technical dependencies of
each.&lt;/p&gt;
&lt;p&gt;As an example, suppose a site supports the following inventory types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Desktop leaderboard&lt;/li&gt;
&lt;li&gt;Mobile banner&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To load and display ads, the example site uses the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A consent management platform&lt;/li&gt;
&lt;li&gt;Audience scripts&lt;/li&gt;
&lt;li&gt;Header bidding scripts&lt;/li&gt;
&lt;li&gt;A rendering framework&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, create a flowchart for each inventory type to visualize how the various
dependencies interact in order to load and display an ad. Desktop leaderboard
inventory may look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/fast-ads-matter/desktop-leaderboard.svg&quot; alt=&quot;An example workflow for the desktop leaderboard inventorytype.&quot;&gt;&lt;/p&gt;
&lt;p&gt;While a more complex inventory type, such as mobile banner, may look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/fast-ads-matter/mobile-banner.svg&quot; alt=&quot;An example workflow for the mobile banner inventorytype.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then, use this information to create a simple table like the one below, which
maps each inventory type to its dependencies in an easily digestible format.&lt;/p&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;table class=&quot;w-table--middle-align&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type of inventory&lt;/th&gt;
&lt;th&gt;Consent management platform&lt;/th&gt;
&lt;th&gt;Audience script&lt;/th&gt;
&lt;th&gt;Header bidding script&lt;/th&gt;
&lt;th&gt;Rendering framework&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Desktop leaderboard&lt;/td&gt;
&lt;td&gt;&amp;#x2714;&lt;/td&gt;
&lt;td&gt;&amp;#x2714; (X)&lt;/td&gt;
&lt;td&gt;&amp;#x2714; (A)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile banner&lt;/td&gt;
&lt;td&gt;&amp;#x2714;&lt;/td&gt;
&lt;td&gt;&amp;#x2714; (X and Y)&lt;/td&gt;
&lt;td&gt;&amp;#x2714; (A and B)&lt;/td&gt;
&lt;td&gt;&amp;#x2714;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Creating an overview of inventory types and dependencies like this helps to
identify critical paths and areas for optimization. For example, you may find
that some dependencies are included unnecessarily and can be removed for a quick
speed improvement. This information is especially useful to have when analyzing
ad loading times.&lt;/p&gt;
&lt;h3 id=&quot;know-where-you-want-to-improve&quot;&gt;Know where you want to improve &lt;a class=&quot;w-headline-link&quot; href=&quot;#know-where-you-want-to-improve&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A good way to approach improving ad speed is to focus on reducing the amount of
time it takes for the first ad on your page to load. This time can be broken
down into three main intervals:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Time to load ad libraries&lt;/dt&gt;
&lt;dd&gt;The time it takes to load all ad libraries necessary to issue the first ad request. May be improved by removing or &lt;a href=&quot;https://web.dev/efficiently-load-third-party-javascript/&quot;&gt;delaying the loading of scripts&lt;/a&gt; that are not related to making ad requests.&lt;/dd&gt;
&lt;dt&gt;Time to first ad request&lt;/dt&gt;
&lt;dd&gt;The time elapsed from ad library load to the first ad request being made. May be improved by parallelizing header bidding requests and avoiding tasks that &lt;a href=&quot;https://web.dev/mainthread-work-breakdown/&quot;&gt;block the main thread&lt;/a&gt;.&lt;/dd&gt;
&lt;dt&gt;Time to render first ad&lt;/dt&gt;
&lt;dd&gt;The time elapsed from the first ad request being made to the first ad being rendered. May be improved by reducing ad complexity and creative file size.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Before you start making any changes, you need to decide which of these metrics
to focus on. While the ultimate goal is to minimize them all, the relative
importance of improving each (and the methods you use to do so) will greatly
depend on your specific setup.&lt;/p&gt;
&lt;p&gt;You can use a tool like &lt;a href=&quot;https://developers.google.com/publisher-ads-audits&quot;&gt;Publisher Ads Audits for
Lighthouse&lt;/a&gt; to help you
analyze your site, identify bottlenecks, and make an informed decision about
what to focus your efforts on.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you understand the importance of ad speed and have a framework for
thinking about it, it&#39;s time to identify areas for improvement in your sites and
make your ads fast. Finally, consider authoring your ads in
&lt;a href=&quot;https://amp.dev/about/ads/&quot;&gt;AMP&lt;/a&gt;, a format that reliably produces fast ads.&lt;/p&gt;
</content>
<author>
<name>Gustav Ernberg von Heijne</name>
</author><author>
<name>Jonathon Imperiosi</name>
</author><author>
<name>Rob Hazan</name>
</author><author>
<name>Beng Eu</name>
</author>
</entry>
<entry>
<title>Notification Triggers</title>
<link href="https://web.dev/notification-triggers/"/>
<updated>2019-10-23T17:00:00-07:00</updated>
<id>https://web.dev/notification-triggers/</id>
<content type="html">&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The Notification Triggers API, part of Google&#39;s
&lt;a href=&quot;https://developers.google.com/web/updates/capabilities&quot;&gt;capabilities project&lt;/a&gt;,
is currently in development. This post will be updated as the
implementation progresses.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;what&quot;&gt;What are Notification Triggers? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Web developers can display notifications using the
&lt;a href=&quot;https://www.w3.org/TR/notifications/&quot;&gt;Web Notifications API&lt;/a&gt;. This feature is often used
with the &lt;a href=&quot;https://w3c.github.io/push-api/&quot;&gt;Push API&lt;/a&gt; to inform the user of
time-sensitive information, such as breaking news events or received messages. Notifications are
shown by running JavaScript on the user&#39;s device.&lt;/p&gt;
&lt;p&gt;The problem with the Push API is that it&#39;s not reliable for triggering notifications which &lt;em&gt;must&lt;/em&gt; be
shown when a particular condition, like time or location, is met. An example of a &lt;em&gt;time-based
condition&lt;/em&gt; is a calendar notification that reminds you of an important meeting with your boss at
2:00 PM. An example of a &lt;em&gt;location-based condition&lt;/em&gt; is when you enter the vicinity of your grocery
store, you get a reminder notification to buy milk. Network connectivity or battery-preserving
features like doze mode can delay the delivery of push based notifications.&lt;/p&gt;
&lt;p&gt;Notification Triggers solve this problem by letting you schedule notifications with their triggering
condition in advance, so that the operation system will deliver the notification at the right time
even if there is no network connectivity or the device is in battery saver mode.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;For now, only &lt;em&gt;time-based triggers&lt;/em&gt; are supported in Chrome. Additional triggers, such as
location-based triggers, for example, will potentially be added in the future based on developer
demand.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;use-cases&quot;&gt;Use cases &lt;a class=&quot;w-headline-link&quot; href=&quot;#use-cases&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Calendar applications can use time-based notification triggers to remind a user of upcoming
meetings. The default notification scheme for a calendar app could be to show a first &amp;quot;heads up&amp;quot;
notification one hour before a meeting and then another more urgent notification five minutes
before.&lt;/p&gt;
&lt;p&gt;A TV network might remind users that their favorite TV show is about to start, or a conference live
stream is about to begin.&lt;/p&gt;
&lt;p&gt;Time zone conversion sites can use time-based notification triggers to let their users schedule
alarms for telephone conferences or video calls.&lt;/p&gt;
&lt;h2 id=&quot;status&quot;&gt;Current status &lt;a class=&quot;w-headline-link&quot; href=&quot;#status&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Create explainer&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/beverloo/notification-triggers/blob/master/README.md&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Create initial draft of specification&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Gather feedback &amp;amp; iterate on design&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;#feedback&quot;&gt;In progress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Origin trial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href=&quot;https://developers.chrome.com/origintrials/&quot;&gt;In Progress&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Launch&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;use&quot;&gt;How to use notification triggers &lt;a class=&quot;w-headline-link&quot; href=&quot;#use&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;enabling-via-chrome:flags&quot;&gt;Enabling via chrome://flags &lt;a class=&quot;w-headline-link&quot; href=&quot;#enabling-via-chrome:flags&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To experiment with the Notification Triggers API locally, without an origin trial token, enable the
&lt;code&gt;#enable-experimental-web-platform-features&lt;/code&gt; flag in &lt;code&gt;chrome://flags&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;enabling-support-during-the-origin-trial-phase&quot;&gt;Enabling support during the origin trial phase &lt;a class=&quot;w-headline-link&quot; href=&quot;#enabling-support-during-the-origin-trial-phase&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Starting in Chrome 80, the Notification Triggers API will be available as an origin trial.
Origin trials allow you to try new features and give feedback on their usability, practicality, and
effectiveness, both to us, and to the web standards community. For more information, see the
&lt;a href=&quot;https://googlechrome.github.io/OriginTrials/developer-guide.html&quot;&gt;Origin Trials Guide for Web Developers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Request a &lt;a href=&quot;https://developers.chrome.com/origintrials/&quot;&gt;token&lt;/a&gt; for your origin. Add the token to
your pages, there are two ways to provide this token on any pages in your origin: Add an
&lt;code&gt;origin-trial&lt;/code&gt; &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag to the &lt;code&gt;head&lt;/code&gt; of any page. For example, this may look something like:
&lt;code&gt;&amp;lt;meta http-equiv=&amp;quot;origin-trial&amp;quot; content=&amp;quot;TOKEN_GOES_HERE&amp;quot;&amp;gt;&lt;/code&gt; If you can configure your server, you
can also provide the token on pages using an &lt;code&gt;Origin-Trial&lt;/code&gt; HTTP header. The resulting response
header should look something like: &lt;code&gt;Origin-Trial: TOKEN_GOES_HERE&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;w-headline-link&quot; href=&quot;#feature-detection&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can find out if the browser supports Notification Triggers by checking for the existence of the
&lt;code&gt;showTrigger&lt;/code&gt; property.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;showTrigger&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;/* Notification Triggers supported */&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;scheduling-a-notification&quot;&gt;Scheduling a notification &lt;a class=&quot;w-headline-link&quot; href=&quot;#scheduling-a-notification&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Scheduling a notification is similar to showing a regular push notification, with the difference
being that you need to pass a &lt;code&gt;showTrigger&lt;/code&gt; condition property with a &lt;code&gt;TimestampTrigger&lt;/code&gt; object as
the value to the notification&#39;s options object.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;createScheduledNotification&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRegistration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showNotification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; body&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This notification was scheduled 30 seconds ago&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; showTrigger&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimestampTrigger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;On desktop, notification triggers fire only if Chrome is running.
On Android, they fire regardless.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;canceling-a-scheduled-notification&quot;&gt;Canceling a scheduled notification &lt;a class=&quot;w-headline-link&quot; href=&quot;#canceling-a-scheduled-notification&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To cancel scheduled notifications, first request a list of all notifications that match a
certain tag through &lt;code&gt;ServiceWorkerRegistration.getNotifications()&lt;/code&gt;. Note that you need to pass the
&lt;code&gt;includeTriggered&lt;/code&gt; flag for scheduled notifications to be included in the list.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;cancelScheduledNotification&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRegistration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; notifications &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getNotifications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; includeTriggered&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; notifications&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;notification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; notification&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;debugging&quot;&gt;Debugging &lt;a class=&quot;w-headline-link&quot; href=&quot;#debugging&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can use the &lt;a href=&quot;https://developers.google.com/web/updates/2019/07/devtools#backgroundservices&quot;&gt;Chrome DevTools Notifications pane&lt;/a&gt; to debug notifications.
To start debugging, press &lt;strong&gt;Start recording events&lt;/strong&gt; &lt;img src=&quot;https://web.dev/notification-triggers/record.png&quot; alt=&quot;Start recording events&quot;&gt; or
&lt;kbd&gt;Control&lt;/kbd&gt;+&lt;kbd&gt;E&lt;/kbd&gt; (&lt;kbd&gt;Command&lt;/kbd&gt;+&lt;kbd&gt;E&lt;/kbd&gt; on Mac). Chrome DevTools
records all notification events, including scheduled, displayed, and closed notifications,
for 3 days, even when DevTools is closed.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--fullbleed&quot;&gt;
&lt;img src=&quot;https://web.dev/notification-triggers/devtools-scheduled.png&quot; alt=&quot;A scheduled notification event was logged to the Notifications pane of Chrome
DevTools, which is located in the Application panel.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
A scheduled notification.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-figure w-figure--fullbleed&quot;&gt;
&lt;img src=&quot;https://web.dev/notification-triggers/devtools-displayed.png&quot; alt=&quot;A displayed notification event was logged to the Notifications pane of Chrome
DevTools.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
A displayed notification.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;w-headline-link&quot; href=&quot;#demo&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can see Notification Triggers in action in the &lt;a href=&quot;https://notification-triggers.glitch.me/&quot;&gt;demo&lt;/a&gt;, which allows you to schedule
notifications, list scheduled notifications, and cancel them. The source code is available on
&lt;a href=&quot;https://glitch.com/edit/#!/notification-triggers&quot;&gt;Glitch&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--fullbleed&quot;&gt;
&lt;img src=&quot;https://web.dev/notification-triggers/demo.png&quot; alt=&quot;A screenshot of the Notification Triggers demo web app.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
The Notification Triggers &lt;a href=&quot;https://notification-triggers.glitch.me/&quot;&gt;demo&lt;/a&gt;.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;security-and-permissions&quot;&gt;Security and permissions &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-and-permissions&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chrome team has designed and implemented the Notification Triggers API using the core principles defined in
&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/lkgr/docs/security/permissions-for-powerful-web-platform-features.md&quot;&gt;Controlling Access to Powerful Web Platform Features&lt;/a&gt;, including user control,
transparency, and ergonomics. Because this API requires service workers,
it also requires a secure context.
Using the API requires the same permission as regular push notifications.&lt;/p&gt;
&lt;h3 id=&quot;user-control&quot;&gt;User control &lt;a class=&quot;w-headline-link&quot; href=&quot;#user-control&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This API is only available in the context of a &lt;code&gt;ServiceWorkerRegistration&lt;/code&gt;. This implies that all
required data is stored in the same context and is automatically deleted when the service worker is
deleted or the user deletes all site data for the origin. Blocking cookies also prevents service
workers from being installed in Chrome, and therefore this API from being used.
Notifications can always be disabled by the user for the site in site settings.&lt;/p&gt;
&lt;h3 id=&quot;transparency&quot;&gt;Transparency &lt;a class=&quot;w-headline-link&quot; href=&quot;#transparency&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unlike the Push API, this API does not depend on the network, which implies scheduled notifications
need all required data beforehand, including image resources referenced by the &lt;code&gt;badge&lt;/code&gt;,
&lt;code&gt;icon&lt;/code&gt; and &lt;code&gt;image&lt;/code&gt; attributes. This means showing a scheduled notification is not observable by the
developer and doesn&#39;t involve waking up the service worker until the user interacts with the
notification. Consequently, there is currently no known way the developer could obtain information
about the user through potentially privacy-invading approaches like IP address geolocation lookup.
This design also allows the feature to optionally tap into scheduling mechanisms provided by
the operating system like Android&#39;s
&lt;a href=&quot;https://developer.android.com/reference/android/app/AlarmManager&quot;&gt;&lt;code&gt;AlarmManager&lt;/code&gt;&lt;/a&gt;, which helps
preserve battery.&lt;/p&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;w-headline-link&quot; href=&quot;#feedback&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chrome team wants to hear about your experiences with Notification Triggers.&lt;/p&gt;
&lt;h3 id=&quot;tell-us-about-the-api-design&quot;&gt;Tell us about the API design &lt;a class=&quot;w-headline-link&quot; href=&quot;#tell-us-about-the-api-design&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Is there something about the API that doesn&#39;t work like you expected? Or are there missing methods
or properties that you need to implement your idea? Have a question or comment on the security
model? File a spec issue on the &lt;a href=&quot;https://github.com/beverloo/notification-triggers/issues&quot;&gt;Notification Triggers GitHub repo&lt;/a&gt;, or add your thoughts to
an existing issue.&lt;/p&gt;
&lt;h3 id=&quot;problem-with-the-implementation&quot;&gt;Problem with the implementation? &lt;a class=&quot;w-headline-link&quot; href=&quot;#problem-with-the-implementation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you find a bug with Chrome&#39;s implementation? Or is the implementation different from the spec?
File a bug at &lt;a href=&quot;https://new.crbug.com/&quot;&gt;new.crbug.com&lt;/a&gt;. Be sure to include as much detail as you can,
simple instructions for reproducing, and set Components to &lt;code&gt;UI&amp;gt;Notifications&lt;/code&gt;. Glitch works great
for sharing quick and easy repros.&lt;/p&gt;
&lt;h3 id=&quot;planning-to-use-the-api&quot;&gt;Planning to use the API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#planning-to-use-the-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Planning to use Notification Triggers on your site? Your public support helps us to prioritize
features, and shows other browser vendors how critical it is to support them. Send a Tweet to
&lt;a href=&quot;https://twitter.com/chromiumdev&quot;&gt;@ChromiumDev&lt;/a&gt; with &lt;code&gt;#notificationtriggers&lt;/code&gt; and let us know where
and how you&#39;re using it.&lt;/p&gt;
&lt;h2 id=&quot;helpful&quot;&gt;Helpful Links &lt;a class=&quot;w-headline-link&quot; href=&quot;#helpful&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/beverloo/notification-triggers/blob/master/README.md&quot;&gt;Public explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://notification-triggers.glitch.me/&quot;&gt;Notification Triggers demo&lt;/a&gt; | &lt;a href=&quot;https://glitch.com/edit/#!/notification-triggers&quot;&gt;Notification Triggers demo source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=891339&quot;&gt;Tracking bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/5133150283890688&quot;&gt;ChromeStatus.com entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blink Component: &lt;code&gt;UI&amp;gt;Notifications&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;w-headline-link&quot; href=&quot;#acknowledgements&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Notification Triggers was implemented by &lt;a href=&quot;https://uk.linkedin.com/in/richardknoll&quot;&gt;Richard Knoll&lt;/a&gt;
and the explainer written by &lt;a href=&quot;https://twitter.com/beverloo?lang=en&quot;&gt;Peter Beverloo&lt;/a&gt;, with
contributions from Richard. The following people have reviewed the article:
&lt;a href=&quot;https://twitter.com/medleyjp&quot;&gt;Joe Medley&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/petele&quot;&gt;Pete LePage&lt;/a&gt;, as well as
Richard and Peter.&lt;/p&gt;
</content>
<author>
<name>Thomas Steiner</name>
</author>
</entry>
<entry>
<title>Verify phone numbers on the web with the SMS Receiver API</title>
<link href="https://web.dev/sms-receiver-api-announcement/"/>
<updated>2019-10-06T17:00:00-07:00</updated>
<id>https://web.dev/sms-receiver-api-announcement/</id>
<content type="html">&lt;h2 id=&quot;what-is-the-sms-receiver-api&quot;&gt;What is the SMS Receiver API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-is-the-sms-receiver-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These days, most people in the world own a mobile device and developers are
commonly using phone numbers as an identifier for users of their services.&lt;/p&gt;
&lt;p&gt;There are a variety of ways to verify phone numbers, but a randomly generated
one-time password (OTP) sent by SMS to the number is one of the most common.
Sending this code back to the developer&#39;s server demonstrates control of the
phone number.&lt;/p&gt;
&lt;p&gt;This idea is already deployed in many scenarios to achieve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phone number as an identifier for the user.&lt;/strong&gt; When signing up for a new
service, some websites ask for a phone number instead of an email address and
use it as an account identifier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two step verification.&lt;/strong&gt; When signing in, a website asks for a one-time code
sent via SMS on top of a password or other knowledge factor for extra
security.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payment confirmation.&lt;/strong&gt; When a user is making a payment, asking for a
one-time code sent via SMS can help verify the person&#39;s intent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The current process creates friction for the user. Finding an OTP within an SMS
message, then copying and pasting it to the form is cumbersome, lowering
conversion rates in critical user journeys. Easing this has been a long standing
request for the web from many of the largest global developers. Android has &lt;a href=&quot;https://developers.google.com/identity/sms-retriever/&quot;&gt;an
API that does exactly
this&lt;/a&gt;. So does
&lt;a href=&quot;https://developer.apple.com/documentation/security/password_autofill/about_the_password_autofill_workflow&quot;&gt;iOS&lt;/a&gt;
and
&lt;a href=&quot;https://developer.apple.com/documentation/security/password_autofill/enabling_password_autofill_on_an_html_input_element&quot;&gt;Safari&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The SMS Receiver API lets your app receive specially-formatted messages bound to
your app&#39;s origin. From this, you can programmatically obtain an OTP from an SMS
message and verify a phone number for the user more easily. Sign up for the
&lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/607985949695016961&quot;&gt;Origin
Trial&lt;/a&gt;
now if you are interested!&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--warning&quot;&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt;
Attackers can spoof SMS and can hijack a person&#39;s phone
number. Carriers can also recycle phone numbers to new users after an account
was closed. While SMS OTP is useful to verify a phone number for the use cases
above, we recommend using additional and stronger forms of authentication (such
as multiple factors and &lt;a href=&quot;https://www.w3.org/TR/webauthn/&quot;&gt;WebAuthn&lt;/a&gt;) to
establish new sessions for these users.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;current-status&quot;&gt;Current status &lt;a class=&quot;w-headline-link&quot; href=&quot;#current-status&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The table below explains the current status of the SMS Receiver API.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th markdown=&quot;block&quot;&gt;
Step
&lt;/th&gt;
&lt;th markdown=&quot;block&quot;&gt;
Status
&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
1. Create explainer
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;a href=&quot;https://github.com/samuelgoto/sms-receiver/blob/master/README.md&quot;&gt;Complete&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
2. Create initial draft of specification
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
In Progress
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
3. Gather feedback and iterate on design
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
In Progress
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;strong&gt;4. Origin trial&lt;/strong&gt;
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;strong&gt;Starts in Chrome 78&lt;/strong&gt;&lt;br&gt;
Expected to run through Chrome 81
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
5. Launch
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
Not started
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h2 id=&quot;see-it-in-action&quot;&gt;See it in action &lt;a class=&quot;w-headline-link&quot; href=&quot;#see-it-in-action&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s say a user wants to verify their phone number with a website. The website
sends a text message to the user over SMS and the user enters the OTP from the
message to verify the ownership of the phone number.&lt;/p&gt;
&lt;p&gt;With the SMS Receiver API, these steps will become as easy as one tap for the
user, as demonstrated in the video. As the text message arrives, a bottom
sheet pops up and prompts the user to verify their
phone number. After clicking the &lt;strong&gt;Verify&lt;/strong&gt; button within the bottom sheet,
the browser pastes the OTP into the form and the form is submitted without
the user needing to press &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/p&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/sms-receiver-announce/demo.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/sms-receiver-announce/demo.webm&quot; type=&quot;video/webm&quot;&gt;
&lt;/video&gt;
&lt;p&gt;The whole process is diagrammed in the image below.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/sms-receiver-api-announcement/diagram.png&quot; width=&quot;486&quot; height=&quot;499&quot;&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
SMS Receiver API diagram
&lt;/figcaption&gt;
&lt;/figure&gt;Try &lt;a href=&quot;https://sms-receiver-demo.glitch.me/&quot;&gt;the demo&lt;/a&gt; yourself. It doesn&#39;t ask for
your phone number or send an SMS to your device, but you can send one from another
device by copying the text displayed on the demo. This works because it doesn&#39;t matter who
the sender is when using the SMS Receiver API.&lt;p&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prepare Google Chrome on Android 78 or later with the &lt;strong&gt;Experimental Web Platform
features&lt;/strong&gt; flag turned on at
&lt;code&gt;chrome://flags/#enable-experimental-web-platform-features&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Go to
&lt;a href=&quot;https://sms-receiver-demo.glitch.me/&quot;&gt;https://sms-receiver-demo.glitch.me&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Select your browser channel from the provided list.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Copy&lt;/strong&gt; to copy the text message and send it to another phone.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Verify&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;From the other phone, send yourself the copied text message via SMS.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Did you receive the SMS and see the prompt to enter the code to the input area?
That is how the SMS Receiver API works for end users.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--warning&quot;&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt;
If you are using a Work Profile on your Android device and the SMS Receiver does
not seem to be working, try installing and using Chrome on your personal profile
instead (i.e. the same profile in which you receive SMS messages).&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;using-the-sms-receiver-api&quot;&gt;Using the SMS Receiver API &lt;a class=&quot;w-headline-link&quot; href=&quot;#using-the-sms-receiver-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using the SMS Receiver API consists of two parts: JavaScript code in your web
app and the formatted message text sent via SMS.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The SMS Receiver API requires a secure origin (HTTPS).&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;w-headline-link&quot; href=&quot;#feature-detection&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Feature detection is much the same as for many other APIs:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;sms&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;process-the-otp&quot;&gt;Process the OTP &lt;a class=&quot;w-headline-link&quot; href=&quot;#process-the-otp&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The SMS Receiver API itself is simple enough:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sms &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Once a user taps &lt;strong&gt;Verify&lt;/strong&gt;, displayed in the bottom sheet, the promise
containing the entire text message will resolve. You can use a regular
expression to extract the OTP and verify the user. Notably, you should parse and
use the SMS message assuming it could have been altered by an attacker inserting
their own SMSes into your app (e.g. following the formatting convention and
sending it right after you called &lt;code&gt;navigator.sms.receive()&lt;/code&gt;). For example, if a
text message contains a six digit verification code following &lt;code&gt;otp=&lt;/code&gt;, the code
would look like this:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; code &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sms&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;/^[\s\S]*otp=([0-9]{6})[\s\S]*$/m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;You can now submit the code to the server to verify it.&lt;/p&gt;
&lt;h3 id=&quot;formatting-the-sms-message&quot;&gt;Formatting the SMS message &lt;a class=&quot;w-headline-link&quot; href=&quot;#formatting-the-sms-message&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The API itself should have looked simple enough, but a critical part is to
format your SMS text message according to a specific convention. The message has
to be sent after &lt;code&gt;navigator.sms.receive()&lt;/code&gt; is called and must comply with a
formatting convention.&lt;/p&gt;
&lt;p&gt;The SMS message must be received on the device where &lt;code&gt;navigator.sms.receive()&lt;/code&gt;
was called.&lt;/p&gt;
&lt;p&gt;The message must adhere to the following formatting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The origin part of the URL of the website that invoked the API. It must be
preceded by &lt;code&gt;For:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The URL must contain (for &lt;a href=&quot;https://github.com/samuelgoto/sms-receiver/issues/4#issuecomment-528991114&quot;&gt;the time
being&lt;/a&gt;)
a query parameter whose value is the application hash of the user&#39;s Chrome
instance. (These are static strings. See the table below.)&lt;/li&gt;
&lt;li&gt;The URL must contain a query parameter &lt;code&gt;otp&lt;/code&gt; whose value is the OTP.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An example message that can be retrieved by the browser would look like this:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;Your OTP is: 123456.&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;For: https://sms-receiver-demo.glitch.me/?otp=123456&amp;xFJnfg75+8v&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The application hash of Chrome instances are static. Use one of these strings
for development depending on which Chrome build you will be working with.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th markdown=&quot;block&quot;&gt;
&lt;strong&gt;Chrome build&lt;/strong&gt;
&lt;/th&gt;
&lt;th markdown=&quot;block&quot;&gt;
&lt;strong&gt;APK hash string&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
Chrome Beta
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;code&gt;xFJnfg75+8v&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td markdown=&quot;block&quot;&gt;
Chrome Stable
&lt;/td&gt;
&lt;td markdown=&quot;block&quot;&gt;
&lt;code&gt;EvsSSj4C6vl&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h3 id=&quot;demos&quot;&gt;Demos &lt;a class=&quot;w-headline-link&quot; href=&quot;#demos&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Try various messages with the demo:&lt;br&gt;
&lt;a href=&quot;https://sms-receiver-demo.glitch.me/&quot;&gt;https://sms-receiver-demo.glitch.me&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You may also fork it and create your version:&lt;br&gt;
&lt;a href=&quot;https://glitch.com/edit/#!/sms-receiver-demo&quot;&gt;https://glitch.com/edit/#!/sms-receiver-demo&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;enabling-support-during-the-origin-trial&quot;&gt;Enabling support during the origin trial &lt;a class=&quot;w-headline-link&quot; href=&quot;#enabling-support-during-the-origin-trial&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Starting in Chrome 78, &lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/607985949695016961&quot;&gt;the SMS Receiver API is available as an origin trial on
Chrome for
Android&lt;/a&gt;.
Origin trials allow you to try new features and give feedback on their
usability, practicality, and effectiveness, both to the Chrome team and to the
web standards community. For more information, see the &lt;a href=&quot;https://googlechrome.github.io/OriginTrials/developer-guide.html&quot;&gt;Origin Trials Guide for
Web
Developers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To participate in an origin trial:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request a
&lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/607985949695016961&quot;&gt;token&lt;/a&gt;
for your origin.&lt;/li&gt;
&lt;li&gt;Add the token to your pages, there are two ways to provide this token on any
page in your origin:
&lt;ul&gt;
&lt;li&gt;Add an &lt;code&gt;origin-trial &amp;lt;meta&amp;gt;&lt;/code&gt; tag to the head of any page:&lt;br&gt;
&lt;code&gt;&amp;lt;meta http-equiv=&amp;quot;origin-trial&amp;quot; content=&amp;quot;TOKEN_GOES_HERE&amp;quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If you can configure your server, you can also provide the token on pages
using an &lt;code&gt;Origin-Trial&lt;/code&gt; HTTP header:&lt;br&gt;
&lt;code&gt;Origin-Trial: TOKEN_GOES_HERE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;w-headline-link&quot; href=&quot;#feedback&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We want to hear about your experiences with the SMS Receiver API. Please sign up
for the &lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/607985949695016961&quot;&gt;Origin
Trial&lt;/a&gt;
now!&lt;/p&gt;
&lt;h3 id=&quot;tell-us-about-the-api-design&quot;&gt;Tell us about the API design &lt;a class=&quot;w-headline-link&quot; href=&quot;#tell-us-about-the-api-design&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Is there something about the API that doesn&#39;t work as expected? Or are there
missing methods or properties that you need to implement your idea?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File an issue on the &lt;a href=&quot;https://github.com/samuelgoto/sms-receiver&quot;&gt;SMS Receiver API explainer GitHub
repo&lt;/a&gt;, or add your thoughts to an
existing issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;problem-with-the-implementation&quot;&gt;Problem with the implementation? &lt;a class=&quot;w-headline-link&quot; href=&quot;#problem-with-the-implementation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you find a bug with Chrome&#39;s implementation?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File a bug at
&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/entry?components=Blink%3EContacts&quot;&gt;https://new.crbug.com&lt;/a&gt;.
Include as much detail as you can, simple instructions for reproducing, and
set &lt;strong&gt;Components&lt;/strong&gt; to &lt;code&gt;Blink&amp;gt;SMS&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;planning-to-use-the-api&quot;&gt;Planning to use the API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#planning-to-use-the-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Planning to use the SMS Receiver API? Your public support helps us prioritize
features, and shows other browser vendors how critical it is to support them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be sure you have signed up for the &lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/607985949695016961&quot;&gt;SMS Receiver Origin
Trial&lt;/a&gt;
to show your interest and provide your domain and contact info.&lt;/li&gt;
&lt;li&gt;Send a Tweet to &lt;a href=&quot;https://twitter.com/chromiumdev&quot;&gt;@ChromiumDev&lt;/a&gt; with
&lt;code&gt;#smsreceiver&lt;/code&gt; and let us know where and how you&#39;re using it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ &lt;a class=&quot;w-headline-link&quot; href=&quot;#faq&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;why-did-you-not-align-with-safari&#39;s-one-time-code&quot;&gt;Why did you not align with Safari&#39;s &lt;code&gt;one-time-code&lt;/code&gt;? &lt;a class=&quot;w-headline-link&quot; href=&quot;#why-did-you-not-align-with-safari&#39;s-one-time-code&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We&#39;re exploring options similar to Safari&#39;s declarative
&lt;a href=&quot;https://developer.apple.com/documentation/security/password_autofill/enabling_password_autofill_on_an_html_input_element&quot;&gt;&lt;code&gt;autocomplete=&amp;quot;one-time-code&amp;quot;&lt;/code&gt;&lt;/a&gt;
approach as we go through our origin trial (&lt;a href=&quot;https://chromium-review.googlesource.com/c/chromium/src/+/1639728&quot;&gt;early
exploration&lt;/a&gt;)
and we are interested to hear what developers and users think. Our imperative
approach could provide a more flexible UX and reduce friction when verifying a phone
number under certain circumstances. The declarative approach is easier to
implement for developers, but requires a form field and at least several taps:
focus on the input field, select the one-time-code, then submit the form. The
approach we are exploring (inspired by what native &lt;a href=&quot;https://developers.google.com/identity/sms-retriever/overview&quot;&gt;Android
apps&lt;/a&gt; have access
to) means that people make only a single tap on browser UI inline on the page
content.&lt;/p&gt;
&lt;h3 id=&quot;is-it-safe-to-use-sms-as-a-way-to-authenticate&quot;&gt;Is it safe to use SMS as a way to authenticate? &lt;a class=&quot;w-headline-link&quot; href=&quot;#is-it-safe-to-use-sms-as-a-way-to-authenticate&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While SMS OTP is useful to verify a phone number when the number is first
provided, phone number verification via SMS must be used carefully for returning
user re-authentication since phone numbers can be hijacked and recycled by
carriers. SMS OTP is a convenient re-auth and recovery mechanism, but services
should combine it with additional factors, such as a knowledge challenge, or use
&lt;a href=&quot;https://www.w3.org/TR/webauthn/&quot;&gt;WebAuthn&lt;/a&gt; for strong authentication.&lt;/p&gt;
&lt;h3 id=&quot;can&#39;t-we-omit-the-browser&#39;s-app-hash&quot;&gt;Can&#39;t we omit the browser&#39;s app hash? &lt;a class=&quot;w-headline-link&quot; href=&quot;#can&#39;t-we-omit-the-browser&#39;s-app-hash&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We would &lt;a href=&quot;https://github.com/samuelgoto/sms-receiver/issues/4#issuecomment-528991114&quot;&gt;like to remove
it&lt;/a&gt;,
but it&#39;s currently a platform restriction. We are working with the Android
team to understand what&#39;s the best way to approach it.&lt;/p&gt;
&lt;h3 id=&quot;will-an-sms-message-timeout&quot;&gt;Will an SMS message timeout? &lt;a class=&quot;w-headline-link&quot; href=&quot;#will-an-sms-message-timeout&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Yes. We&#39;re planning to use &lt;code&gt;AbortController&lt;/code&gt; to time the request out (&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=976401&quot;&gt;tracking
bug&lt;/a&gt;), but it&#39;s
not implemented as of Chrome 78.&lt;/p&gt;
&lt;h3 id=&quot;will-the-apk-hash-change-for-an-installed-pwa&quot;&gt;Will the apk hash change for an installed PWA? &lt;a class=&quot;w-headline-link&quot; href=&quot;#will-the-apk-hash-change-for-an-installed-pwa&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;No. A PWA&#39;s app hash is the same as the browser it runs in.&lt;/p&gt;
&lt;h3 id=&quot;can-we-localize-the-&amp;quot;for:&amp;quot;-string-required-in-the-sms&quot;&gt;Can we localize the &amp;quot;For:&amp;quot; string required in the SMS? &lt;a class=&quot;w-headline-link&quot; href=&quot;#can-we-localize-the-&amp;quot;for:&amp;quot;-string-required-in-the-sms&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not right now. But ultimately, we are planning to remove it or otherwise allow for
localization.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Find more questions at &lt;a href=&quot;https://github.com/samuelgoto/sms-receiver/blob/master/FAQ.md&quot;&gt;the FAQ section in the explainer&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content>
<author>
<name>Eiji Kitamura</name>
</author>
</entry>
<entry>
<title>Preloading responsive images</title>
<link href="https://web.dev/preload-responsive-images/"/>
<updated>2019-09-29T17:00:00-07:00</updated>
<id>https://web.dev/preload-responsive-images/</id>
<content type="html">&lt;p&gt;This article gives me an opportunity to discuss two of my favorite things: responsive images &lt;em&gt;and&lt;/em&gt; preload. As someone who was heavily involved in developing both of those features, I&#39;m super excited to see them working together!&lt;/p&gt;
&lt;h2 id=&quot;responsive-images-overview&quot;&gt;Responsive images overview &lt;a class=&quot;w-headline-link&quot; href=&quot;#responsive-images-overview&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Suppose you&#39;re browsing the web on a screen that&#39;s 300 pixels wide, and the page just requested an image that&#39;s 1500 pixels wide. That page just wasted a lot of your cellular data because your screen can&#39;t do anything with all of that extra resolution. Ideally, the browser should fetch a version of the image that&#39;s just a &lt;em&gt;little&lt;/em&gt; wider than your screen size, say 325 pixels. This ensures a high-resolution image without wasting data. And, even better, the image will load faster. &lt;a href=&quot;https://web.dev/serve-responsive-images/#serve-multiple-image-versions&quot;&gt;Responsive images&lt;/a&gt; enable browsers to fetch different image resources to different devices. If you don&#39;t use an &lt;a href=&quot;https://web.dev/image-cdns/&quot;&gt;image CDN&lt;/a&gt; need to save multiple dimensions for each image and specify them in the &lt;code&gt;srcset&lt;/code&gt; attribute. The &lt;code&gt;w&lt;/code&gt; value tells the browser the width of each version. Depending on the device, the browser can choose the appropriate one:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;small.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;small.jpg 500w, medium.jpg 1000w, large.jpg 1500w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;preload-overview&quot;&gt;Preload overview &lt;a class=&quot;w-headline-link&quot; href=&quot;#preload-overview&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/preload-critical-assets&quot;&gt;Preload&lt;/a&gt; lets you tell the browser about critical resources that you want to load as soon as possible, before they are discovered in HTML. This is especially useful for resources that are not easily discoverable, such as fonts included in stylesheets, background images, or resources loaded from a script.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;preload&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;important.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;responsive-images-+-preload-faster-image-loads&quot;&gt;Responsive images + preload = faster image loads &lt;a class=&quot;w-headline-link&quot; href=&quot;#responsive-images-+-preload-faster-image-loads&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Responsive images and preload have been available for the last few years, but at the same time something was missing: there was no way to preload responsive images. &lt;a href=&quot;https://developers.google.com/web/updates/2019/03/nic73#more&quot;&gt;Starting in Chrome 73&lt;/a&gt;, the browser can preload the right variant of responsive images specified in &lt;code&gt;srcset&lt;/code&gt; before it discovers the &lt;code&gt;img&lt;/code&gt; tag!&lt;/p&gt;
&lt;p&gt;Depending on your site&#39;s structure, that could mean significantly faster image display! We ran tests on a site that uses Javascript to lazy-load responsive images. Preloading resulted in images loading 1.2 seconds faster.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Responsive images are &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Browser_compatibility&quot;&gt;supported in all modern browsers&lt;/a&gt; while preloading them is &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content#Browser_compatibility&quot;&gt;supported only in Chromium-based browsers&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;imagesrcset-and-imagesizes&quot;&gt;&lt;code&gt;imagesrcset&lt;/code&gt; and &lt;code&gt;imagesizes&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#imagesrcset-and-imagesizes&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To preload responsive images, new attributes were recently added to the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element: &lt;code&gt;imagesrcset&lt;/code&gt; and &lt;code&gt;imagesizes&lt;/code&gt;. They are used with &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot;&amp;gt;&lt;/code&gt; and match the &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; syntax used in &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;For example, if you want to preload a responsive image specified with:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;wolf.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;50vw&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;A rad wolf&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;You can do that by adding the following to your HTML&#39;s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;preload&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;wolf.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;imagesrcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;imagesizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;50vw&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;This kicks off a request using the same resource selection logic that &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; will apply.&lt;/p&gt;
&lt;h2 id=&quot;use-cases&quot;&gt;Use cases &lt;a class=&quot;w-headline-link&quot; href=&quot;#use-cases&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;preloading-dynamically-injected-responsive-images&quot;&gt;Preloading dynamically-injected responsive images &lt;a class=&quot;w-headline-link&quot; href=&quot;#preloading-dynamically-injected-responsive-images&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&#39;s say you&#39;re dynamically-loading hero images as part of a slideshow and know which image will be displayed first. In that case, you probably want to avoid waiting for the script before loading the image in question, as that would delay when users can see it.&lt;/p&gt;
&lt;p&gt;You can inspect this issue on a website with a dynamically-loaded image gallery:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open &lt;a href=&quot;https://responsive-preload.glitch.me/no_preload.html&quot;&gt;this example website&lt;/a&gt; in a new tab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press &lt;code&gt;Control+Shift+J&lt;/code&gt; (or &lt;code&gt;Command+Option+J&lt;/code&gt; on Mac) to open DevTools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click the &lt;strong&gt;Network&lt;/strong&gt; tab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;strong&gt;Throttling&lt;/strong&gt; drop-down list, select &lt;strong&gt;Fast 3G&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Disable the &lt;strong&gt;Disable cache&lt;/strong&gt; checkbox.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reload the page.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/preload-responsive-images/example-1-before.png&quot; alt=&quot;Screenshot of Chrome DevTools Network panel.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;This waterfall shows that the images only start loading after the browser has finished running the script, introducing unnecessary delay to the time the image is initially displayed to the user.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Using &lt;code&gt;preload&lt;/code&gt; helps here because the image starts loading ahead of time and is likely to already be there when the browser needs to display it.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/preload-responsive-images/example-1-after.png&quot; alt=&quot;Screenshot of Chrome DevTools Network panel.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;This waterfall shows that the first image started loading at the same time as the script, avoiding unnecessary delays and resulting in faster displaying images.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To see the difference that preloading makes, you can inspect the same dynamically-loaded image gallery but &lt;a href=&quot;https://responsive-preload.glitch.me/preload.html&quot;&gt;with preloaded first image&lt;/a&gt; by following the steps from the first example.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;An alternative way to avoid the problem would be to use a markup-based carousel and have the &lt;a href=&quot;https://hacks.mozilla.org/2017/09/building-the-dom-faster-speculative-parsing-async-defer-and-preload/&quot;&gt;browser&#39;s preloader&lt;/a&gt; pick up the required resources. However, this approach may not always be practical. (For example, if you are reusing an existing component, which is not markup-based.)&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;preloading-background-images-using-image-set&quot;&gt;Preloading background images using image-set &lt;a class=&quot;w-headline-link&quot; href=&quot;#preloading-background-images-using-image-set&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you have different background images for different screen densities, you can specify them in your CSS with the &lt;code&gt;image-set&lt;/code&gt; syntax. The browser can then choose which one to display based on the screen&#39;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio&quot;&gt;DPR&lt;/a&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;image-set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cat.png&quot;&lt;/span&gt; 1x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cat-2x.png&quot;&lt;/span&gt; 2x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The above syntax ignores the fact that vendor prefixes are needed for this feature in both Chromium and WebKit based browsers. If you&#39;re planning to use this feature, you should consider using &lt;a href=&quot;https://github.com/postcss/autoprefixer&quot;&gt;Autoprefixer&lt;/a&gt; to address that automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The problem with CSS background images is that they are discovered by the browser only after it has downloaded and processed all the CSS in the page&#39;s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, which can be a lot of CSS…&lt;/p&gt;
&lt;p&gt;You can inspect this issue on an example website with &lt;a href=&quot;https://responsive-preload.glitch.me/background_no_preload.html&quot;&gt;responsive background image&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/preload-responsive-images/example-2-before.png&quot; alt=&quot;Screenshot of Chrome DevTools Network panel.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;In this example, the image download doesn&#39;t start until the CSS is fully downloaded, resulting in unnecessary lag to the image&#39;s display.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Responsive image preloading provides a simple and hack-free way to load those images faster.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;preload&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;cat.png&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;image&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;imagesrcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;cat.png 1x, cat-2x.png 2x&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;You can inspect how the previous example behaves with &lt;a href=&quot;https://responsive-preload.glitch.me/background_preload.html&quot;&gt;preloaded responsive background image&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/preload-responsive-images/example-2-after.png&quot; alt=&quot;Screenshot of Chrome DevTools Network panel.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;Here the image and CSS start downloading at the same time, avoiding delays and resulting in a faster loading image.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;preloading-responsive-images-in-action&quot;&gt;Preloading responsive images in action &lt;a class=&quot;w-headline-link&quot; href=&quot;#preloading-responsive-images-in-action&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Preloading your responsive images can speed them up in theory, but what does it do in practice?&lt;/p&gt;
&lt;p&gt;To answer that I created two copies of a &lt;a href=&quot;https://github.com/GoogleChromeLabs/sample-pie-shop&quot;&gt;demo PWA shop&lt;/a&gt;: &lt;a href=&quot;https://20190710t144416-dot-pie-shop-app.appspot.com/apparel&quot;&gt;one that does not preload images&lt;/a&gt;, and &lt;a href=&quot;https://20190710t132936-dot-pie-shop-app.appspot.com/apparel&quot;&gt;one that preloads some of them&lt;/a&gt;. Since the site lazy loads images using JavaScript, it&#39;s likely to benefit from preloading the ones that will be in the initial viewport.&lt;/p&gt;
&lt;p&gt;That gave me the following results for &lt;a href=&quot;https://www.webpagetest.org/result/190710_VM_30b9d4c993a1e60befba17e1261ba1ca/&quot;&gt;no preload&lt;/a&gt; and for &lt;a href=&quot;https://www.webpagetest.org/result/190710_7B_a99e792121760f81a270b4b9c847797b/&quot;&gt;image preload&lt;/a&gt;. Looking at the raw numbers we see that &lt;a href=&quot;https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/quick-start-quide#TOC-Start-Render:&quot;&gt;Start Render&lt;/a&gt; stayed the same, &lt;a href=&quot;https://web.dev/speed-index/&quot;&gt;Speed Index&lt;/a&gt; slightly improved (273 ms, as images arrive faster, but don&#39;t take up a huge chunk of the pixel area), but the real metric which captures the difference is the &lt;a href=&quot;https://github.com/WPO-Foundation/webpagetest/blob/master/docs/Metrics/HeroElements.md&quot;&gt;Last Painted Hero&lt;/a&gt; metric, which improved by 1.2 seconds. 🎉🎉&lt;/p&gt;
&lt;p&gt;Of course, nothing captures the visual difference quite like a filmstrip comparison:&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/preload-responsive-images/example-3.png&quot; alt=&quot;Screenshot of WebPageTest filmstrip comparison showing preloaded images are displayed about 1.5 seconds faster.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;The filmstrip shows that images arrive significantly faster when preloaded, resulting in a hugely-improved user experience.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;preload-and-lesspicturegreater&quot;&gt;Preload and &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;? &lt;a class=&quot;w-headline-link&quot; href=&quot;#preload-and-lesspicturegreater&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re familiar with responsive images, you may be wondering &amp;quot;What about &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;&lt;/a&gt;?&amp;quot;.&lt;/p&gt;
&lt;p&gt;The Web Performance Working Group is talking about adding a preload equivalent for &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt;, but not the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element, which tackles the &lt;a href=&quot;https://web.dev/codelab-art-direction/&quot;&gt;&amp;quot;art direction&amp;quot;&lt;/a&gt; use-case.&lt;/p&gt;
&lt;p&gt;Why is this use-case being &amp;quot;neglected&amp;quot;?&lt;/p&gt;
&lt;p&gt;While there&#39;s interest in solving that use case as well, there are still a number of &lt;a href=&quot;https://calendar.perfplanet.com/2018/how-the-sausage-is-made-webperfwg-meeting-summary/&quot;&gt;technical issues to sort out&lt;/a&gt; which means that a solution here would have significant complexity. On top of that, it seems like for the most part, the use-case can be addressed today, even if in a hacky way (see below).&lt;/p&gt;
&lt;p&gt;Given that, the Web Performance WG decided to ship &lt;code&gt;srcset&lt;/code&gt; first and see if the demand for equivalent &lt;code&gt;picture&lt;/code&gt; support arises.&lt;/p&gt;
&lt;p&gt;If you do find yourself in a position to preload &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; you may be able to use the following technique as a workaround.&lt;/p&gt;
&lt;p&gt;Given the following scenario:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;small_cat.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(max-width: 400px)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;medium_cat.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(max-width: 800px)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;huge_cat.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element&#39;s logic (or the image source selection logic, to be precise), would be to go over the &lt;code&gt;media&lt;/code&gt; attributes of the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements in order, find the first one that matches, and use the attached resource.&lt;/p&gt;
&lt;p&gt;Because responsive preload has no notion of &amp;quot;order&amp;quot; or &amp;quot;first match&amp;quot;, the breakpoints need to be translated into something like:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;preload&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;small_cat.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(max-width: 400px)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;preload&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;medium_cat.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(min-width: 400.1px) and (max-width: 800px)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;preload&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;large_cat.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(min-width: 800.1px)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;w-headline-link&quot; href=&quot;#summary&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Responsive image preload gives us new and exciting possibilities to preload responsive images in ways that were previously only possible using hacks. It&#39;s an important new addition to the speed-conscious developer&#39;s toolbox and enables us to make sure the important images we want to get in front of our users as soon as possible will be there when we need them.&lt;/p&gt;
</content>
<author>
<name>Yoav Weiss</name>
</author>
</entry>
<entry>
<title>Fixing layout instability</title>
<link href="https://web.dev/fixing-layout-instability/"/>
<updated>2019-09-29T17:00:00-07:00</updated>
<id>https://web.dev/fixing-layout-instability/</id>
<content type="html">&lt;p&gt;In an earlier post I wrote about &lt;a href=&quot;https://dev.to/chromiumdev/measuring-cumulative-layout-shift-cls-in-webpagetest-5cle&quot;&gt;measuring Cumulative Layout Shift&lt;/a&gt; (CLS) in WebPageTest. CLS is an aggregation of all layout shifts, so in this post I thought it&#39;d be interesting to dive deeper and inspect each individual layout shift on a page to try to understand what could be causing the instability and actually try to fix the issue(s).&lt;/p&gt;
&lt;h2 id=&quot;measuring-layout-shifts&quot;&gt;Measuring layout shifts &lt;a class=&quot;w-headline-link&quot; href=&quot;#measuring-layout-shifts&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using the Layout Instability API, we can get a list of all layout shift events on a page:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hadRecentInput&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;layout-shift&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; buffered&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;This produces an array of layout shifts that are not preceded by input events:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;entryType&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;layout-shift&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;startTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;210.78500000294298&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;duration&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0001045969445437389&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;hadRecentInput&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;lastInputTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;In this example there was a single very tiny shift of 0.01% at 210ms.&lt;/p&gt;
&lt;p&gt;Knowing the time and severity of the shift is useful to help narrow down what could have caused the shift. Let&#39;s turn back to &lt;a href=&quot;https://webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt; for a lab environment to do more testing.&lt;/p&gt;
&lt;h2 id=&quot;measuring-layout-shifts-in-webpagetest&quot;&gt;Measuring layout shifts in WebPageTest &lt;a class=&quot;w-headline-link&quot; href=&quot;#measuring-layout-shifts-in-webpagetest&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Similar to measuring CLS in WebPageTest, measuring individual layout shifts will require a custom metric. Fortunately, the process is easier now that Chrome 77 is stable. The Layout Instability API is enabled by default, so you should be able to execute that JS snippet on any website within Chrome 77 and get results immediately. In WebPageTest, you can use the default Chrome browser and not have to worry about command line flags or using Canary.&lt;/p&gt;
&lt;p&gt;So let&#39;s modify that script to produce a custom metric for WebPageTest:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;LayoutShifts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hadRecentInput&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;layout-shift&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; buffered&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The promise in this script resolves to a JSON representation of the array rather than the array itself. This is because custom metrics can only produce primitive data types like strings or numbers.&lt;/p&gt;
&lt;p&gt;The website I&#39;ll use for the test is &lt;a href=&quot;https://ismyhostfastyet.com/&quot;&gt;ismyhostfastyet.com&lt;/a&gt;, a site I built to compare real world loading performance of web hosts.&lt;/p&gt;
&lt;h2 id=&quot;identifying-causes-of-layout-instability&quot;&gt;Identifying causes of layout instability &lt;a class=&quot;w-headline-link&quot; href=&quot;#identifying-causes-of-layout-instability&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the &lt;a href=&quot;http://webpagetest.org/custom_metrics.php?test=190918_6E_ef3c166b4a34033171d47e389cf82939&amp;amp;run=5&amp;amp;cached=0&quot;&gt;results&lt;/a&gt; we can see the LayoutShifts custom metric has this value:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;entryType&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;layout-shift&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;startTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3087.2349999990547&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;duration&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.3422101449275362&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;hadRecentInput&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;lastInputTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;To summarize, there is a single layout shift of 34.2% happening at 3087ms. To help identify the culprit, let&#39;s use WebPageTest&#39;s &lt;a href=&quot;http://webpagetest.org/video/compare.php?tests=190918_6E_ef3c166b4a34033171d47e389cf82939-r%3A5-c%3A0&amp;amp;thumbSize=200&amp;amp;ival=100&amp;amp;end=visual&quot;&gt;filmstrip view&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/fixing-layout-instability/layout-shift1.png&quot; alt=&quot;Two cells in the filmstrip, showing screenshots before and after the layout shift.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;Two cells in the filmstrip, showing screenshots before and after the layout shift.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Scrolling to the ~3 second mark in the filmstrip shows us exactly what the cause of the 34% layout shift is: the colorful table. The website asynchronously fetches a JSON file, then renders it to a table. The table is initially empty, so waiting to fill it up when the results are loaded is causing the shift.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/fixing-layout-instability/layout-shift2.png&quot; alt=&quot;Web font header appearing out of nowhere.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;Web font header appearing out of nowhere.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;But that&#39;s not all. When the page is visually complete at ~4.3 seconds, we can see that the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; of the page &amp;quot;Is my host fast yet?&amp;quot; appears out of nowhere. This happens because the site uses a web font and hasn&#39;t taken any steps to optimize rendering. The layout doesn&#39;t actually appear to shift when this happens, but it&#39;s still a poor user experience to have to wait so long to read the title.&lt;/p&gt;
&lt;h2 id=&quot;fixing-layout-instability&quot;&gt;Fixing layout instability &lt;a class=&quot;w-headline-link&quot; href=&quot;#fixing-layout-instability&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we know the asynchronously generated table is causing one-third of the viewport to shift, it&#39;s time to fix it. We don&#39;t know the contents of the table until the JSON results are actually loaded, but we can still populate the table with some kind of &lt;em&gt;placeholder data&lt;/em&gt; so that the layout itself is relatively stable when the DOM is rendered.&lt;/p&gt;
&lt;p&gt;Here&#39;s the code to generate placeholder data:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomFiller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;maxLength&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; filler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;█&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; maxLength&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;len&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomDistribution&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; avg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; fast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; slow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fast &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; avg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;fast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; avg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slow&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Temporary placeholder data.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;fast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; avg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slow&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomDistribution&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; platform&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomFiller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; client&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomFiller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; n&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRandomFiller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; fast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; avg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; slow&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;updateResultsTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sortResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;fast&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The placeholder data is generated randomly before being sorted. It includes the &amp;quot;█&amp;quot; character repeated a random number of times to create visual placeholders for the text and a randomly generated distribution of the three main values. I also added some styles to desaturate all color from the table to make it clear that the data is not fully loaded yet.&lt;/p&gt;
&lt;p&gt;The appearance of the placeholders you use don&#39;t matter for layout stability. The purpose of the placeholders is to assure users that content &lt;em&gt;is&lt;/em&gt; coming and the page isn&#39;t broken.&lt;/p&gt;
&lt;p&gt;Here&#39;s what the placeholders look like while the JSON data is loading:&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/fixing-layout-instability/layout-placeholder.png&quot; alt=&quot;The data table is rendered with placeholder data.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;The data table is rendered with placeholder data.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Addressing the web font issue is much simpler. Because the site is using Google Fonts, we just need to pass in the &lt;code&gt;display=swap&lt;/code&gt; property in the CSS request. That&#39;s all. The Fonts API will add the &lt;code&gt;font-display: swap&lt;/code&gt; style in the font declaration, enabling the browser to render text in a fallback font immediately. Here&#39;s the corresponding markup with the fix included:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://fonts.googleapis.com/css?family=Chivo:900&amp;amp;display=swap&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;verifying-the-optimizations&quot;&gt;Verifying the optimizations &lt;a class=&quot;w-headline-link&quot; href=&quot;#verifying-the-optimizations&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After rerunning the page through WebPageTest, we can generate a before and after &lt;a href=&quot;http://webpagetest.org/video/compare.php?tests=190918_6E_ef3c166b4a34033171d47e389cf82939%2C190918_WF_60f9c9a1c669b20039860c09ca27df7c&amp;amp;thumbSize=200&amp;amp;ival=100&amp;amp;end=visual&quot;&gt;comparison&lt;/a&gt; to visualize the difference and measure the new degree of layout instability:&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/fixing-layout-instability/layout-comparison.png&quot; alt=&quot;WebPageTest filmstrip showing both sites loading side-by-side with and without layout optimizations.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;WebPageTest filmstrip showing both sites loading side-by-side with and without layout optimizations.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;entryType&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;layout-shift&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;startTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3070.9349999997357&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;duration&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.000050272187989256116&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;hadRecentInput&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;lastInputTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;According to the &lt;a href=&quot;http://webpagetest.org/custom_metrics.php?test=190918_WF_60f9c9a1c669b20039860c09ca27df7c&amp;amp;run=9&amp;amp;cached=0&quot;&gt;custom metric&lt;/a&gt;, there is still a layout shift occurring at 3071ms (about the same time as before) but the severity of the shift is &lt;em&gt;much&lt;/em&gt; smaller: 0.005%. I can live with this.&lt;/p&gt;
&lt;p&gt;It&#39;s also clear from the filmstrip that the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; font is immediately falling back to a system font, enabling users to read it sooner.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Complex websites will probably experience many more layout shifts than in this example, but the remediation process is still the same: add layout instability metrics to WebPageTest, cross-reference the results with the visual loading filmstrip to identify the culprits, and implement a fix using placeholders to reserve the screen real estate.&lt;/p&gt;
&lt;h3 id=&quot;(one-more-thing)-measuring-layout-instability-experienced-by-real-users&quot;&gt;(One more thing) Measuring layout instability experienced by real users &lt;a class=&quot;w-headline-link&quot; href=&quot;#(one-more-thing)-measuring-layout-instability-experienced-by-real-users&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It&#39;s nice to be able to run WebPageTest on a page before and after an optimization and see an improvement to a metric, but what really matters is that the user experience is actually getting better. Isn&#39;t that why we&#39;re trying to make the site better in the first place?&lt;/p&gt;
&lt;p&gt;So what would be great is if we start measuring the layout instability experiences of real users along with our traditional web performance metrics. This is a crucial piece of the optimization feedback loop because having data from the field tells us where the problems are and whether our fixes made a positive difference.&lt;/p&gt;
&lt;p&gt;In addition to collecting your own layout instability data, check out the &lt;a href=&quot;https://twitter.com/ChromeUXReport/status/1138555303379816448&quot;&gt;Chrome UX Report&lt;/a&gt;, which includes Cumulative Layout Shift data from real user experiences on millions of websites. It allows you to find out how you (or your competitors) are performing, or you can use it to explore the state of layout instability across the web.&lt;/p&gt;
</content>
<author>
<name>Rick Viscomi</name>
</author>
</entry>
<entry>
<title>Optimize images with Thumbor</title>
<link href="https://web.dev/use-thumbor/"/>
<updated>2019-09-22T17:00:00-07:00</updated>
<id>https://web.dev/use-thumbor/</id>
<content type="html">&lt;p&gt;&lt;a href=&quot;http://thumbor.org/&quot;&gt;Thumbor&lt;/a&gt; is a free, open source image CDN that makes it easy to compress, resize, and transform images. This post lets you try out Thumbor firsthand without needing to install anything. We&#39;ve set up a sandbox Thumbor server for you to try out at &lt;code&gt;http://34.67.235.246:8888&lt;/code&gt;. The image that you&#39;re going to experiment with is available at &lt;a href=&quot;http://34.67.235.246:8888/unsafe/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;http://34.67.235.246:8888/unsafe/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;prequisites&quot;&gt;Prequisites &lt;a class=&quot;w-headline-link&quot; href=&quot;#prequisites&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This post assumes that you understand how image CDNs can improve your load performance. If not, check out &lt;a href=&quot;https://web.dev/image-cdns&quot;&gt;Use image CDNs to optimize images&lt;/a&gt;. It also assumes that you&#39;ve built basic websites before.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;If you would like to install Thumbor on your own server and then follow along with this post, check out &lt;a href=&quot;https://web.dev/install-thumbor&quot;&gt;How to install the Thumbor image CDN&lt;/a&gt;. Whenever you see &lt;code&gt;http://34.67.235.246:8888&lt;/code&gt; in this post you&#39;ll need to replace that origin with your Thumbor instance&#39;s origin.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;thumbor-url-format&quot;&gt;Thumbor URL Format &lt;a class=&quot;w-headline-link&quot; href=&quot;#thumbor-url-format&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned in &lt;a href=&quot;https://web.dev/image-cdns&quot;&gt;Use Image CDNs to Optimize Images&lt;/a&gt;, each image CDN uses a slightly different URL format for images. Figure 1 represents Thumbor&#39;s format.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor0.jpg&quot; alt=&quot;A Thumbor URL has the following components: origin, security key, size, filters and image.&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Thumbor&#39;s URL format&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;origin&quot;&gt;Origin &lt;a class=&quot;w-headline-link&quot; href=&quot;#origin&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like all &lt;a href=&quot;https://html.spec.whatwg.org/multipage/origin.html#concept-origin&quot;&gt;origins&lt;/a&gt;, the origin of a Thumbor URL is composed of three parts: a &lt;a href=&quot;https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Definition&quot;&gt;scheme&lt;/a&gt; (which is almost always &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt;), a host, and a port. In this example, the host is identified using an IP address, but if you&#39;re using a DNS server it might look like &lt;code&gt;thumbor-server.my-site.com&lt;/code&gt;. By default, Thumbor uses port &lt;code&gt;8888&lt;/code&gt; to serve images.&lt;/p&gt;
&lt;h3 id=&quot;security-key&quot;&gt;Security Key &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-key&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;unsafe&lt;/code&gt; part of the URL indicates that you&#39;re using Thumbor without a security key. A security key prevents a user from making unauthorized changes to your image URLs. By changing the image URL, a user could use your server (and your hosting bill) to resize their images, or, more maliciously, to overload your server. This guide won&#39;t cover setting up &lt;a href=&quot;https://github.com/thumbor/thumbor/wiki/security&quot;&gt;Thumbor&#39;s security key feature&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;size&quot;&gt;Size &lt;a class=&quot;w-headline-link&quot; href=&quot;#size&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This part of the URL specifies the desired size of the output image. This can be omitted if you don&#39;t want to change the size of the image. Thumbor will use different approaches like cropping or scaling to achieve the desired size depending on the other URL parameters. The next section of this post explains how to resize images in more detail.&lt;/p&gt;
&lt;p&gt;Try it now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Click the following URL to view the image served at its original size in a new tab: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor1.jpg&quot; alt=&quot;Image at original size&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Original image&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resize the image to 100x100 pixels: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/100x100/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/100x100/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor2.jpg&quot; alt=&quot;Image at 100x100 pixels&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Image resized to 100x100 pixels&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;filters&quot;&gt;Filters &lt;a class=&quot;w-headline-link&quot; href=&quot;#filters&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Filters transform an image. The filters part of the URL segment starts with &lt;code&gt;filters:&lt;/code&gt; followed by a colon-separated list of filters; this can be omitted if you are not using any filters. The syntax for individual filters resembles a function call (for example &lt;code&gt;grayscale()&lt;/code&gt;) containing zero or more arguments.&lt;/p&gt;
&lt;p&gt;Try it now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Apply a single filter: a Gaussian &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/blur.html&quot;&gt;blur&lt;/a&gt; effect with a radius of 25 pixels: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/filters:blur(25)/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/filters:blur(25)/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor3.jpg&quot; alt=&quot;Blurred image&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Blurred image&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply multiple filter. Convert to &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/grayscale.html&quot;&gt;grayscale&lt;/a&gt; and &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/rotate.html&quot;&gt;rotate&lt;/a&gt; the image 90 degrees: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/filters:grayscale():blur(90)/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/filters:grayscale():blur(90)/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor4.jpg&quot; alt=&quot;Grayscale image that has been rotated 90 degrees&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Grayscale, rotated image&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;transforming-images&quot;&gt;Transforming Images &lt;a class=&quot;w-headline-link&quot; href=&quot;#transforming-images&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This section focuses on the Thumbor functionalities most relevant to performance: compression, resizing, and conversion between file formats.&lt;/p&gt;
&lt;h3 id=&quot;compression&quot;&gt;Compression &lt;a class=&quot;w-headline-link&quot; href=&quot;#compression&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/quality.html&quot;&gt;quality&lt;/a&gt; filter compresses JPEG images to the desired image quality level (1-100). If no quality level is provided, Thumbor compresses the image to a quality level of 80. This is a good default: quality levels 80-85 typically have little noticeable effect on image quality, but usually decrease image size by 30-40%.&lt;/p&gt;
&lt;p&gt;Try it now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Compress the image to a quality of 1 (very bad): &lt;a href=&quot;http://34.67.235.246:8888/unsafe/filters:quality(1)/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/filters:quality(1)/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor5.jpg&quot; alt=&quot;Low-quality image&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Low-quality image&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compress the image using Thumbor&#39;s default compression settings: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/filters:quality()/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/filters:quality()/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor6.jpg&quot; alt=&quot;Compressed image with no noticible quality issues&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Compressed image&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;resizing&quot;&gt;Resizing &lt;a class=&quot;w-headline-link&quot; href=&quot;#resizing&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To resize an image while maintaining its original proportions use the format &lt;code&gt;$WIDTHx0&lt;/code&gt; or &lt;code&gt;0x$HEIGHT&lt;/code&gt; within the &lt;code&gt;size&lt;/code&gt; portion of the URL string.&lt;/p&gt;
&lt;p&gt;Try it now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Resize the image to a width of 200 pixels while maintaining original proportions: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/200x0/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/200x0/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;!-- lint disable code-block-style --&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor7.jpg&quot; alt=&quot;Image that is 200 pixels wide&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Image resized to a width of 200 pixels&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resize the image to a height of 500 pixels while maintaining original proportion: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/0x500/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/0x500/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor8.jpg&quot; alt=&quot;Image that is 500 pixels tall&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Image resized to a height of 500 pixels&lt;figcaption&gt;
&lt;/figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;You can also resize images to a percentage of the original by using the &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/proportion.html&quot;&gt;proportion&lt;/a&gt; filter. If size is specified in conjunction with the proportion filter, the image will be resized, and then the proportion filter will be applied.&lt;/p&gt;
&lt;p&gt;Try it now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Resize the image to 50% of the original: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/filters:proportion(.5)/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/filters:proportion(.5)/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor9.jpg&quot; alt=&quot;Image that is 50% the size of the original&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Image resized to 50% the size of the original&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resize the image to a width of 1000 pixels, then resize the image to 10% of its current size: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/1000x/filters:proportion(.1)/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/1000x/filters:proportion(.1)/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor10.jpg&quot; alt=&quot;Image that is 100 pixels wide&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;Image resized to a width of 100 pixels&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;These methods are just a few of Thumbor&#39;s many cropping and resizing options. To read about other options, check out &lt;a href=&quot;https://github.com/thumbor/thumbor/wiki/Usage&quot;&gt;Usage&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;file-formats&quot;&gt;File Formats &lt;a class=&quot;w-headline-link&quot; href=&quot;#file-formats&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/format.html&quot;&gt;format&lt;/a&gt; filter converts images to &lt;code&gt;jpeg&lt;/code&gt;, &lt;code&gt;webp&lt;/code&gt;, &lt;code&gt;gif&lt;/code&gt;, or &lt;code&gt;png&lt;/code&gt;. Keep in mind that if you&#39;re optimizing for performance you should &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization&quot;&gt;use either JPEG or WebP&lt;/a&gt; as PNG and GIF files tend to be significantly larger and do not compress as well.&lt;/p&gt;
&lt;p&gt;Try it now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Convert the image to WebP. If you open the &lt;strong&gt;Network&lt;/strong&gt; panel of DevTools the document&#39;s &lt;strong&gt;Content-Type response header&lt;/strong&gt; shows that the server returned a WebP image: &lt;a href=&quot;http://34.67.235.246:8888/unsafe/filters:format(webp)/https://web.dev/backdrop-filter/hero.jpg&quot; target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;http://34.67.235.246:8888/unsafe/filters:format(webp)/https://web.dev/backdrop-filter/hero.jpg&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/use-thumbor/Use-thumbor11.jpg&quot; alt=&quot;DevTools screenshot showing the content-type (WebP) of an image&quot; class=&quot;w-screenshot&quot;&gt;
&lt;figcaption&gt;The &lt;code&gt;content-type&lt;/code&gt; response header shown in DevTools&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps &lt;a class=&quot;w-headline-link&quot; href=&quot;#next-steps&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Try out other &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/filters.html&quot;&gt;filters&lt;/a&gt; and transformations on the &lt;code&gt;hero.jpg&lt;/code&gt; image.&lt;/p&gt;
&lt;p&gt;If you&#39;re following along using your own Thumbor installation, check out the appendix below that explains how and why to use the &lt;code&gt;thumbor.conf&lt;/code&gt; file.&lt;/p&gt;
&lt;h2 id=&quot;appendix:-thumbor.conf&quot;&gt;Appendix: &lt;code&gt;thumbor.conf&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#appendix:-thumbor.conf&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many of the configuration options discussed in this post, plus many others, can be established as defaults by setting up and using a &lt;code&gt;thumbor.conf&lt;/code&gt; configuration file. Settings in the &lt;code&gt;thumbor.conf&lt;/code&gt; file will be applied to all images unless overridden by the URL string parameters.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Run the &lt;code&gt;thumbor-config&lt;/code&gt; command to create a new &lt;code&gt;thumbor.conf&lt;/code&gt; file.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;thumbor-config &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ./thumbor.conf&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open your new &lt;code&gt;thumbor.conf&lt;/code&gt; file. The &lt;code&gt;thumbor-config&lt;/code&gt; command generated a file that lists and explains all Thumbor configuration options.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure settings by uncommenting lines and changing the default values.
You may find it useful to set the following settings:&lt;/p&gt;
&lt;!-- lint disable no-inline-padding --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QUALITY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AUTO_WEBP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAX_WIDTH&lt;/code&gt; and &lt;code&gt;MAX_HEIGHT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ALLOW_ANIMATED_GIFS&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run Thumbor with the &lt;code&gt;--conf&lt;/code&gt; flag to use your &lt;code&gt;thumbor.conf&lt;/code&gt; settings.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;thumbor --conf /path/to/thumbor.conf&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
<author>
<name>Katie Hempenius</name>
</author>
</entry>
<entry>
<title>Techniques to make a web app load fast, even on a feature phone</title>
<link href="https://web.dev/load-faster-like-proxx/"/>
<updated>2019-09-22T17:00:00-07:00</updated>
<id>https://web.dev/load-faster-like-proxx/</id>
<content type="html">&lt;p&gt;At Google I/O 2019 Mariko, Jake, and I shipped &lt;a href=&quot;https://proxx.app/&quot;&gt;PROXX&lt;/a&gt;, a modern Minesweeper-clone for the web. Something that sets PROXX apart is the focus on accessibility (you can play it with a screenreader!) and the ability to run as well on a feature phone as on a high-end desktop device. Feature phones are constrained in multiple ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Weak CPUs&lt;/li&gt;
&lt;li&gt;Weak or non-existent GPUs&lt;/li&gt;
&lt;li&gt;Small screens without touch input&lt;/li&gt;
&lt;li&gt;Very limited amounts of memory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But they run a modern browser and are very affordable. For this reason, feature phones are making a resurgence in emerging markets. Their price point allows a whole new audience, who previously couldn&#39;t afford it, to come online and make use of the modern web. &lt;strong&gt;&lt;a href=&quot;https://www.counterpointresearch.com/more-than-a-billion-feature-phones-to-be-sold-over-next-three-years/&quot;&gt;For 2019 it is projected that around 400 million feature phones will be sold in India alone&lt;/a&gt;&lt;/strong&gt;, so users on feature phones might become a significant portion of your audience. In addition to that, connection speeds akin to 2G are the norm in emerging markets. How did we manage to make PROXX work well under feature phone conditions?&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; preload=&quot;metadata&quot; class=&quot;w-screenshot&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/social_supercut_poster.jpg&quot; src=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/social_supercut_x264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
PROXX gameplay.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Performance is important, and that includes both loading performance and runtime performance. It has been shown that &lt;strong&gt;good performance correlates with increased user retention, improved conversions and—most importantly—increased inclusivity.&lt;/strong&gt; &lt;a href=&quot;https://twitter.com/malchata&quot;&gt;Jeremy Wagner&lt;/a&gt; has much more data and insight on &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/why-performance-matters/&quot;&gt;why performance matters&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is part 1 of a two-part series. &lt;strong&gt;Part 1 focuses on loading performance&lt;/strong&gt;, and part 2 will focus on runtime performance.&lt;/p&gt;
&lt;h2 id=&quot;capturing-the-status-quo&quot;&gt;Capturing the status quo &lt;a class=&quot;w-headline-link&quot; href=&quot;#capturing-the-status-quo&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Testing your loading performance on a &lt;em&gt;real&lt;/em&gt; device is critical. If you don&#39;t have a real device at hand, I recommend &lt;a href=&quot;https://webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt; (WPT), specifically the &lt;a href=&quot;https://webpagetest.org/easy&quot;&gt;&amp;quot;simple&amp;quot; setup&lt;/a&gt;. &lt;strong&gt;WPT runs a battery of loading tests on a &lt;em&gt;real&lt;/em&gt; device with an emulated 3G connection.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3G is a good speed to measure. While you might be used to 4G, LTE or soon even 5G, the reality of mobile internet looks quite different. Maybe you&#39;re on a train, at a conference, at a concert, or on a flight. What you&#39;ll be experiencing there is most likely closer to 3G, and sometimes even worse.&lt;/p&gt;
&lt;p&gt;That being said, we&#39;re going to focus on 2G in this article because PROXX is explicitly targeting feature phones and emerging markets in its target audience. Once WebPageTest has run its test, you get a waterfall (similar to what you see in DevTools) as well as a filmstrip at the top. The film strip shows what your user sees while your app is loading. On 2G, the loading experience of the unoptimized version of PROXX is pretty bad:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; muted=&quot;&quot; preload=&quot;metadata&quot; class=&quot;w-screenshot&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/stupid-proxx-load-poster.jpg&quot; src=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/stupid-proxx-load.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
The filmstrip video shows what the user sees when PROXX is loading on a real, low-end device over an emulated 2G connection.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When loaded over 3G, the user sees 4 seconds of white nothingness. &lt;strong&gt;Over 2G the user sees absolutely nothing for over 8 seconds.&lt;/strong&gt; If you read &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/why-performance-matters/&quot;&gt;why performance matters&lt;/a&gt; you know that we have now lost a good portion of our potential users due to impatience. The user needs to download all of the 62 KB of JavaScript for anything to appear on screen. The silver lining in this scenario is that the second anything appears on screen it is also interactive. Or is it?&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;picture&gt;
&lt;source srcset=&quot;https://web.dev/load-faster-like-proxx/proxx-first-render.webp&quot; type=&quot;image/webp&quot;&gt;
&lt;img src=&quot;https://web.dev/load-faster-like-proxx/proxx-first-render.jpg&quot;&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/first-meaningful-paint&quot;&gt;First Meaningful Paint&lt;/a&gt; in the unoptimized version of PROXX is &lt;em&gt;technically&lt;/em&gt; &lt;a href=&quot;https://web.dev/interactive&quot;&gt;interactive&lt;/a&gt; but useless to the user.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After about 62 KB of gzip&#39;d JS has been downloaded and the DOM has been generated, the user gets to see our app. The app is &lt;em&gt;technically&lt;/em&gt; interactive. Looking at the visual, however, shows a different reality. The web fonts are still loading in the background and until they are ready the user can see no text. While this state qualifies as a &lt;a href=&quot;https://web.dev/first-meaningful-paint&quot;&gt;First Meaningful Paint (FMP)&lt;/a&gt;, it surely does not qualify as properly &lt;a href=&quot;https://web.dev/interactive&quot;&gt;interactive&lt;/a&gt;, as the user can&#39;t tell what any of the inputs are about. It takes another second on 3G and 3 seconds on 2G until the app is ready to go. &lt;strong&gt;All in all, the app takes 6 seconds on 3G and 11 seconds on 2G to become interactive.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;waterfall-analysis&quot;&gt;Waterfall analysis &lt;a class=&quot;w-headline-link&quot; href=&quot;#waterfall-analysis&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we know &lt;em&gt;what&lt;/em&gt; the user sees, we need to figure out the &lt;em&gt;why&lt;/em&gt;. For this we can look at the waterfall and analyze why resources are loading too late. In our 2G trace for PROXX we can see two major red flags:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There are multiple, multi-colored thin lines.&lt;/li&gt;
&lt;li&gt;JavaScript files form a chain. For example, the second resource only starts loading once the first resource is finished, and the third resource only starts when the second resource is finished.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;picture&gt;
&lt;img src=&quot;https://web.dev/load-faster-like-proxx/waterfall_opt.png&quot;&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The waterfall gives insight into which resources are loading when and how long they take.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;reducing-connection-count&quot;&gt;Reducing connection count &lt;a class=&quot;w-headline-link&quot; href=&quot;#reducing-connection-count&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Each thin line (&lt;code&gt;dns&lt;/code&gt;, &lt;code&gt;connect&lt;/code&gt;, &lt;code&gt;ssl&lt;/code&gt;) stands for the creation of a new HTTP connection. Setting up a new connection is costly as it takes around 1s on 3G and roughly 2.5s on 2G. In our waterfall we see a new connection for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Request #1: Our &lt;code&gt;index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Request #5: The font styles from &lt;code&gt;fonts.googleapis.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Request #8: Google Analytics&lt;/li&gt;
&lt;li&gt;Request #9: A font file from &lt;code&gt;fonts.gstatic.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Request #14: The Web App Manifest&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The new connection for &lt;code&gt;index.html&lt;/code&gt; is unavoidable. The browser &lt;em&gt;has&lt;/em&gt; to create a connection to our server to get the contents. The new connection for Google Analytics could be avoided by inlining something like &lt;a href=&quot;https://minimalanalytics.com/&quot;&gt;Minimal Analytics&lt;/a&gt;, but Google Analytics is not blocking our app from rendering or becoming interactive, so we don&#39;t really care about how fast it loads. Ideally, Google Analytics should be loaded in idle time, when everything else has already loaded. That way it won&#39;t take up bandwidth or processing power during the initial load. The new connection for the web app manifest is &lt;a href=&quot;https://fetch.spec.whatwg.org/#connections&quot;&gt;prescribed by the fetch spec&lt;/a&gt;, as the manifest has to be loaded over a non-credentialed connection. Again, the web app manifest doesn&#39;t block our app from rendering or becoming interactive, so we don&#39;t need to care that much.&lt;/p&gt;
&lt;p&gt;The two fonts and their styles, however, are a problem as they block rendering and also interactivity. If we look at the CSS that is delivered by &lt;code&gt;fonts.googleapis.com&lt;/code&gt;, it&#39;s just two &lt;code&gt;@font-face&lt;/code&gt; rules, one for each font. The font &lt;em&gt;styles&lt;/em&gt; are so small in fact, that we decided to inline it into our HTML, removing one unnecessary connection. To avoid the cost of the connection setup for the font &lt;em&gt;files&lt;/em&gt;, we can copy them to our own server.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Copying CSS or font files to your own server is okay when using &lt;a href=&quot;https://fonts.google.com/&quot;&gt;Google Fonts&lt;/a&gt;. Other font providers might have different rules. Please check with your font provider&#39;s terms of service!&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;parallelizing-loads&quot;&gt;Parallelizing loads &lt;a class=&quot;w-headline-link&quot; href=&quot;#parallelizing-loads&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Looking at the waterfall, we can see that once the first JavaScript file is done loading, new files start loading immediately. This is typical for module dependencies. Our main module probably has static imports, so the JavaScript cannot run until those imports are loaded. The important thing to realize here is that these kinds of dependencies are known at build time. We can make use of &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot;&amp;gt;&lt;/code&gt; tags to make sure all dependencies start loading the second we receive our HTML.&lt;/p&gt;
&lt;h3 id=&quot;results&quot;&gt;Results &lt;a class=&quot;w-headline-link&quot; href=&quot;#results&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&#39;s take a look at what our changes have achieved. It&#39;s important to not change any other variables in our test setup that could skew the results, so we will be using &lt;a href=&quot;https://webpagetest.org/easy&quot;&gt;WebPageTest&#39;s simple setup&lt;/a&gt; for the rest of this article and look at the filmstrip:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; muted=&quot;&quot; preload=&quot;metadata&quot; class=&quot;w-screenshot&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/preload-proxx-load-poster.jpg&quot; src=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/preload-proxx-load.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
We use WebPageTest&#39;s filmstrip to see what our changes have achieved.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;These changes reduced our TTI from 11 to 8.5&lt;/strong&gt;, which is roughly the 2.5s of connection setup time we aimed to remove. Well done us.&lt;/p&gt;
&lt;h2 id=&quot;prerendering&quot;&gt;Prerendering &lt;a class=&quot;w-headline-link&quot; href=&quot;#prerendering&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While we just reduced our &lt;a href=&quot;https://web.dev/interactive&quot;&gt;TTI&lt;/a&gt;, we haven&#39;t really affected the eternally long white screen the user has to endure for 8.5 seconds. Arguably &lt;strong&gt;the biggest improvements for &lt;a href=&quot;https://web.dev/first-meaningful-paint&quot;&gt;FMP&lt;/a&gt; can be achieved by sending styled markup in your &lt;code&gt;index.html&lt;/code&gt;&lt;/strong&gt;. Common techniques to achieve this are prerendering and server-side rendering, which are closely related and are explained in &lt;a href=&quot;https://developers.google.com/web/updates/2019/02/rendering-on-the-web&quot;&gt;Rendering on the Web&lt;/a&gt;. Both techniques run the web app in Node and serialize the resulting DOM to HTML. Server-side rendering does this per request on the, well, server side, while prerendering does this at build time and stores the output as your new &lt;code&gt;index.html&lt;/code&gt;. Since PROXX is a &lt;a href=&quot;https://jamstack.org/&quot;&gt;JAMStack&lt;/a&gt; app and has no server side, we decided to implement prerendering.&lt;/p&gt;
&lt;p&gt;There are many ways to implement a prerenderer. In PROXX we chose to use &lt;a href=&quot;https://pptr.dev/&quot;&gt;Puppeteer&lt;/a&gt;, which starts Chrome without any UI and allows you to remote control that instance with a Node API. We use this to inject our markup and our JavaScript and then read back the DOM as a string of HTML. Because we are using &lt;a href=&quot;https://github.com/css-modules/css-modules&quot;&gt;CSS Modules&lt;/a&gt;, we get CSS inlining of the styles that we need for free.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; browser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; puppeteer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rawIndexHTML&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;codeToRun&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; renderedHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;index.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; renderedHTML&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;With this in place, we can expect an improvement for our FMP. We still need to load and execute the same amount of JavaScript as before, so we shouldn&#39;t expect TTI to change much. If anything, our &lt;code&gt;index.html&lt;/code&gt; has gotten bigger and might push back our TTI a bit. There&#39;s only one way to find out: running WebPageTest.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; muted=&quot;&quot; preload=&quot;metadata&quot; class=&quot;w-screenshot&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/ssr-proxx-load-poster.jpg&quot; src=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/ssr-proxx-load.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
The filmstrip shows a clear improvement for our FMP metric. TTI is mostly unaffected.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Our First Meaningful Paint has moved from 8.5 seconds to 4.9 seconds,&lt;/strong&gt; a massive improvement. Our TTI still happens at around 8.5 seconds so it has been largely unaffected by this change. What we did here is a &lt;em&gt;perceptual&lt;/em&gt; change. Some might even call it a sleight of hand. By rendering an intermediate visual of the game, we are changing the perceived loading performance for the better.&lt;/p&gt;
&lt;h2 id=&quot;inlining&quot;&gt;Inlining &lt;a class=&quot;w-headline-link&quot; href=&quot;#inlining&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another metric that both DevTools and WebPageTest give us is &lt;a href=&quot;https://web.dev/time-to-first-byte&quot;&gt;Time To First Byte (TTFB)&lt;/a&gt;. This is the time it takes from the first byte of the request being sent to the first byte of the response being received. This time is also often called a Round Trip Time (RTT), although technically there is a difference between these two numbers: RTT does not include the processing time of the request on the server side. &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/network/reference#timing-preview&quot;&gt;DevTools&lt;/a&gt; and WebPageTest visualize TTFB with a light color within the request/response block.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;picture&gt;
&lt;img src=&quot;https://web.dev/load-faster-like-proxx/ttfb.svg&quot;&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The light section of a request signifies the request is waiting to receive the first byte of the response.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Looking at our waterfall, we can see that the &lt;strong&gt;all of requests spend the &lt;em&gt;majority&lt;/em&gt; of their time waiting&lt;/strong&gt; for the first byte of the response to arrive.&lt;/p&gt;
&lt;p&gt;This problem was what HTTP/2 Push was originally conceived for. The app developer &lt;em&gt;knows&lt;/em&gt; that certain resources are needed and can &lt;em&gt;push&lt;/em&gt; them down the wire. By the time the client realizes that it needs to fetch additional resources, they are already in the browser&#39;s caches. &lt;strong&gt;HTTP/2 Push turned out to be too hard to get right and is considered discouraged.&lt;/strong&gt; This problem space will be revisited during the standardization of HTTP/3. For now, &lt;strong&gt;the easiest solution is to &lt;em&gt;inline&lt;/em&gt; all the critical resources&lt;/strong&gt; at the expense of caching efficiency.&lt;/p&gt;
&lt;p&gt;Our critical CSS is already inlined thanks to CSS Modules and our Puppeteer-based prerenderer. For JavaScript we need to inline our critical modules &lt;em&gt;and their dependencies&lt;/em&gt;. This task has varying difficulty, based on the bundler that you&#39;re using.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In this step we also subset our font files to contain only the glyphs that we need for our landing page. I am not going to go into detail on this step as it is not easily abstracted and sometimes not even practical. We still load the full font files lazily, but they are not needed for the initial render.&lt;/p&gt;
&lt;/div&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; muted=&quot;&quot; preload=&quot;metadata&quot; class=&quot;w-screenshot&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/ssr-proxx-load-poster.jpg&quot; src=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/ssr-proxx-load.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
With the inlining of our JavaScript we have reduced our TTI from 8.5s to 7.2s.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This shaved 1 second off our TTI. We have now reached the point where our &lt;code&gt;index.html&lt;/code&gt; contains everything that is needed for the initial render and becoming interactive. The HTML can render while it is still downloading, creating our FMP. The moment the HTML is done parsing and executing, the app is interactive.&lt;/p&gt;
&lt;h2 id=&quot;aggressive-code-splitting&quot;&gt;Aggressive code splitting &lt;a class=&quot;w-headline-link&quot; href=&quot;#aggressive-code-splitting&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yes, our &lt;code&gt;index.html&lt;/code&gt; contains everything that is needed to become interactive. But on closer inspection it turns out it also contains everything else. Our &lt;code&gt;index.html&lt;/code&gt; is around 43 KB. Let&#39;s put that in relation to what the user can interact with at the start: We have a form to configure the game containing a couple of components, a start button and probably some code to persist and load user settings. That&#39;s pretty much it. 43 KB seems like a lot.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;picture&gt;
&lt;source srcset=&quot;https://web.dev/load-faster-like-proxx/proxx-loaded.webp&quot; type=&quot;image/webp&quot;&gt;
&lt;img src=&quot;https://web.dev/load-faster-like-proxx/proxx-loaded.jpg&quot;&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The landing page of PROXX. Only critical components are used here.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To understand where our bundle size is coming from we can use a &lt;a href=&quot;https://npm.im/source-map-explorer&quot;&gt;source map explorer&lt;/a&gt; or a similar tool to break down what the bundle consists of. As predicted, our bundle contains the game logic, the rendering engine, the win screen, the lose screen and a bunch of utilities. Only a small subset of these modules are needed for the landing page. Moving everything that is not strictly required for interactivity into a lazily-loaded module will decrease TTI &lt;em&gt;significantly&lt;/em&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;picture&gt;
&lt;img src=&quot;https://web.dev/load-faster-like-proxx/sourcemap.svg&quot;&gt;
&lt;/picture&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Analyzing the contents of PROXX&#39;s `index.html` shows a lot of unneeded resources. Critical resources are highlighted.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;What we need to do is &lt;a href=&quot;https://web.dev/reduce-javascript-payloads-with-code-splitting/&quot;&gt;code split&lt;/a&gt;. Code splitting breaks apart your monolithic bundle into smaller parts that can be lazy-loaded on-demand. Popular bundlers like &lt;a href=&quot;https://webpack.js.org/&quot;&gt;Webpack&lt;/a&gt;, &lt;a href=&quot;https://rollupjs.org/&quot;&gt;Rollup&lt;/a&gt;, and &lt;a href=&quot;https://parceljs.org/&quot;&gt;Parcel&lt;/a&gt; support code splitting by using dynamic &lt;code&gt;import()&lt;/code&gt;. The bundler will analyze your code and &lt;em&gt;inline&lt;/em&gt; all modules that are imported &lt;em&gt;statically&lt;/em&gt;. Everything that you import &lt;em&gt;dynamically&lt;/em&gt; will be put into its own file and will only be fetched from the network once the &lt;code&gt;import()&lt;/code&gt; call gets executed. Of course hitting the network has a cost and should only be done if you have the time to spare. &lt;strong&gt;The mantra here is to statically import the modules that are &lt;em&gt;critically&lt;/em&gt; needed at load time and dynamically load everything else.&lt;/strong&gt; But you shouldn&#39;t wait to the very last moment to lazy-load modules that are definitely going to be used. &lt;a href=&quot;https://twitter.com/philwalton&quot;&gt;Phil Walton&lt;/a&gt;&#39;s &lt;a href=&quot;https://philipwalton.com/articles/idle-until-urgent/&quot;&gt;Idle Until Urgent&lt;/a&gt; is a great pattern for a healthy middle ground between lazy loading and eager loading.&lt;/p&gt;
&lt;p&gt;In PROXX we created a &lt;code&gt;lazy.js&lt;/code&gt; file that statically imports everything that we &lt;em&gt;don&#39;t&lt;/em&gt; need. In our main file, we can then &lt;em&gt;dynamically&lt;/em&gt; import &lt;code&gt;lazy.js&lt;/code&gt;. However, some of our &lt;a href=&quot;https://preactjs.com/&quot;&gt;Preact&lt;/a&gt; components ended up in &lt;code&gt;lazy.js&lt;/code&gt;, which turned out to be a bit of a complication as Preact can&#39;t handle lazily-loaded components out of the box. For this reason we wrote a little &lt;code&gt;deferred&lt;/code&gt; component wrapper that allows us to render a placeholder until the actual component has loaded.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deferred&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;componentPromise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Deferred&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; LoadedComponent&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; componentPromise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; LoadedComponent&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; component &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; loaded&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; loading &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; LoadedComponent &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LoadedComponent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loaded&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LoadedComponent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;With this in place, we can use a Promise of a component in our &lt;code&gt;render()&lt;/code&gt; functions. For example, the &lt;code&gt;&amp;lt;Nebula&amp;gt;&lt;/code&gt; component, which renders the animated background image, will be replaced by an empty &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; while the component is loading. Once the component is loaded and ready to use, the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; will be replaced with the actual component.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; NebulaDeferred &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deferred&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/components/nebula&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;NebulaDeferred&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; loading&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; loaded&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;Nebula&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Nebula &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;With all of this in place, we reduced our &lt;code&gt;index.html&lt;/code&gt; to a mere 20 KB, less than half of the original size. What effect does this have on FMP and TTI? WebPageTest will tell!&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;video controls=&quot;&quot; muted=&quot;&quot; preload=&quot;metadata&quot; class=&quot;w-screenshot&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/optimized-proxx-load-poster.jpg&quot; src=&quot;https://storage.googleapis.com/web-dev-assets/js-heavy-sites/optimized-proxx-load.mp4&quot; type=&quot;video/mp4; codecs=h264&quot;&gt;
&lt;/video&gt;
&lt;figcaption class=&quot;w-figcaption w-figcaption--fullbleed&quot;&gt;
The filmstrip confirms: Our TTI is now at 5.4s. A drastic improvement from our original 11s.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Our FMP and TTI are only 100ms apart, as it is only a matter of parsing and executing the inlined JavaScript. After just 5.4s on 2G, the app is completely interactive. All the other, less essential modules are loaded in the background.&lt;/p&gt;
&lt;h2 id=&quot;more-sleight-of-hand&quot;&gt;More Sleight of Hand &lt;a class=&quot;w-headline-link&quot; href=&quot;#more-sleight-of-hand&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you look at our list of critical modules above, you&#39;ll see that the rendering engine is not part of the critical modules. Of course, the game cannot start until we have our rendering engine to render the game. We could disable the &amp;quot;Start&amp;quot; button until our rendering engine is ready to start the game, but in our experience the user usually takes long enough to configure their game settings that this isn&#39;t necessary. Most of the time the rendering engine and the other remaining modules are done loading by the time the user presses &amp;quot;Start&amp;quot;. In the rare case that the user is quicker than their network connection, we show a simple loading screen that waits for the remaining modules to finish.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Measuring is important. To avoid spending time on problems that are not real, we recommend to always measure first before implementing optimizations. Additionally, measurements should be done on &lt;em&gt;real&lt;/em&gt; devices on a 3G connection or on &lt;a href=&quot;https://webpagetest.org/easy&quot;&gt;WebPageTest&lt;/a&gt; if no real device is at hand.&lt;/p&gt;
&lt;p&gt;The filmstrip can give insight into how loading your app &lt;em&gt;feels&lt;/em&gt; for the user. The waterfall can tell you what resources are responsible for potentially long loading times. Here&#39;s a checklist of things you can do to improve loading performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deliver as many assets as possible over one connection.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/preload-critical-assets&quot;&gt;Preload&lt;/a&gt; or even inline resources that are required for the first render and interactivity.&lt;/li&gt;
&lt;li&gt;Prerender your app to improve perceived loading performance.&lt;/li&gt;
&lt;li&gt;Make use of aggressive &lt;a href=&quot;https://web.dev/reduce-javascript-payloads-with-code-splitting/&quot;&gt;code splitting&lt;/a&gt; to reduce the amount of code needed for interactivity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stay tuned for part 2 where we discuss how to optimize runtime performance on hyper-constrained devices.&lt;/p&gt;
</content>
<author>
<name>Surma </name>
</author>
</entry>
<entry>
<title>Smarter custom properties with Houdini’s new API</title>
<link href="https://web.dev/css-props-and-vals/"/>
<updated>2019-09-18T17:00:00-07:00</updated>
<id>https://web.dev/css-props-and-vals/</id>
<content type="html">&lt;p&gt;CSS custom properties, also known as &lt;a href=&quot;https://developers.google.com/web/updates/2016/02/css-variables-why-should-you-care&quot;&gt;CSS
variables&lt;/a&gt;,
let you define your own properties in CSS and use their values throughout your
CSS. While incredibly useful today, they have shortcomings that can make them
hard to work with: they can take any value so they may be accidentally
overridden with something unexpected, they always inherit their values from
their parent, and you can&#39;t transition them. With Houdini&#39;s &lt;a href=&quot;https://drafts.css-houdini.org/css-properties-values-api/&quot;&gt;CSS Properties and
Values API Level 1&lt;/a&gt;,
now available in Chrome 78, these shortcomings are transcended, making CSS
custom properties incredibly powerful!&lt;/p&gt;
&lt;h2 id=&quot;what-is-houdini&quot;&gt;What Is Houdini? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-is-houdini&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before talking about the new API, let&#39;s talk about Houdini quickly. The CSS-TAG
Houdini Task Force, better known as CSS Houdini or simply Houdini, exists to
&amp;quot;develop features that explain the &#39;magic&#39; of styling and layout on the web&amp;quot;.
The collection of &lt;a href=&quot;https://drafts.css-houdini.org/&quot;&gt;Houdini specifications&lt;/a&gt; are
designed to open up the power of the browser&#39;s rendering engine, allowing both
deeper insight into our styles and the ability to extend our rendering engine.
With this, typed CSS values in JavaScript and polyfilling or inventing new CSS
without a performance hit are finally possible. Houdini has the potential to
superpower creativity on the web.&lt;/p&gt;
&lt;h2 id=&quot;css-properties-and-values-api-level-1&quot;&gt;CSS Properties and Values API Level 1 &lt;a class=&quot;w-headline-link&quot; href=&quot;#css-properties-and-values-api-level-1&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://drafts.css-houdini.org/css-properties-values-api/&quot;&gt;CSS Properties and Values API Level
1&lt;/a&gt; (Houdini Props and
Vals) allows us to give structure to our custom properties. This is the current
situation when using custom properties:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.thing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;--my-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; green&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Because custom properties don&#39;t have types, they can be overridden in unexpected
ways. For example, consider what happens if you define &lt;code&gt;--my-color&lt;/code&gt; with a URL.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.thing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;--my-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;url(‘not-a-color’)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--my-color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Here, because &lt;code&gt;--my-color&lt;/code&gt; isn&#39;t typed, it doesn&#39;t know that a URL isn&#39;t a valid
color value! When we use it, it falls back to default values (black for &lt;code&gt;color&lt;/code&gt;,
transparent for &lt;code&gt;background&lt;/code&gt;). With Houdini Props and Vals, custom properties can
be &lt;em&gt;registered&lt;/em&gt; so that the browser knows what it &lt;em&gt;should&lt;/em&gt; be!&lt;/p&gt;
&lt;p&gt;Now, the custom property &lt;code&gt;--my-color&lt;/code&gt; is registered as a color! This tells the
browser what kinds of values are allowed and how it can type and treat that
property!&lt;/p&gt;
&lt;h3 id=&quot;anatomy-of-a-registered-property&quot;&gt;Anatomy of a registered property &lt;a class=&quot;w-headline-link&quot; href=&quot;#anatomy-of-a-registered-property&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Registering a property looks like this:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CSS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--my-color&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; syntax&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;color&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; inherits&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; initialValue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;It supports the following options:&lt;/p&gt;
&lt;h4 id=&quot;name:-string&quot;&gt;&lt;code&gt;name: string&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#name:-string&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The name of the custom property.&lt;/p&gt;
&lt;h4 id=&quot;syntax:-string&quot;&gt;&lt;code&gt;syntax: string&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#syntax:-string&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;How to parse the custom property. You can find a complete list of possible values in the &lt;a href=&quot;https://drafts.csswg.org/css-values-3/&quot;&gt;CSS Values and Units&lt;/a&gt; specification. Defaults to &lt;code&gt;*&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;inherits:-boolean&quot;&gt;&lt;code&gt;inherits: boolean&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#inherits:-boolean&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Whether it inherits its parent&#39;s value. Defaults to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;initialvalue:-string&quot;&gt;&lt;code&gt;initialValue: string&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#initialvalue:-string&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Initial value of the custom property.&lt;/p&gt;
&lt;p&gt;Taking a closer look at &lt;code&gt;syntax&lt;/code&gt;. There are a number of &lt;a href=&quot;https://drafts.css-houdini.org/css-properties-values-api/#supported-names&quot;&gt;valid
options&lt;/a&gt;
ranging from numbers to colors to
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident&quot;&gt;&lt;code&gt;&amp;lt;custom-ident&amp;gt;&lt;/code&gt;&lt;/a&gt;
types. These syntaxes can also be modified by using the following values&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Appending &lt;code&gt;+&lt;/code&gt; signifies that it accepts a space-separated list of values of
that syntax. For example, &lt;code&gt;&amp;lt;length&amp;gt;+&lt;/code&gt; would be a space-separated list of
lengths&lt;/li&gt;
&lt;li&gt;Appending&lt;code&gt;#&lt;/code&gt; signifies that it accepts a comma-separated list of values of
that syntax. For example, &lt;code&gt;&amp;lt;color&amp;gt;#&lt;/code&gt; would be a comma-separated list of
colors&lt;/li&gt;
&lt;li&gt;Adding &lt;code&gt;|&lt;/code&gt; between syntaxes or identifiers signifies that any of the provided
options are valid. For example, &lt;code&gt;&amp;lt;color&amp;gt;# | &amp;lt;url&amp;gt; | magic&lt;/code&gt; would allow either
a comma-separated list of colors, a URL, or the word &lt;code&gt;magic&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;gotchas&quot;&gt;Gotchas &lt;a class=&quot;w-headline-link&quot; href=&quot;#gotchas&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are two gotchas with Houdini Props and Vals. The first is that, once
defined, there&#39;s no way to update an existing registered property, and trying to
re-register a property will throw an error indicating that it&#39;s already been
defined.&lt;/p&gt;
&lt;p&gt;Second, unlike standard properties, registered properties aren&#39;t validated when
they&#39;re parsed. Rather they&#39;re validated when they&#39;re computed. That means both
that invalid values won&#39;t appear as invalid when inspecting the element&#39;s
properties, and including an invalid property after a valid one won&#39;t fall back
to the valid one; an invalid property will, however, fall back to the registered
property&#39;s default.&lt;/p&gt;
&lt;h2 id=&quot;animating-custom-properties&quot;&gt;Animating custom properties &lt;a class=&quot;w-headline-link&quot; href=&quot;#animating-custom-properties&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A registered custom property provides a fun bonus beyond type checking: the
ability to animate it! A basic animation example looks like this:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;CSS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--stop-color&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; syntax&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;color&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; inherits&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; initialValue&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;blue&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;--stop-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;--stop-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button:hover&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;--stop-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; green&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;When you hover over the button, it&#39;ll animate from red to green! Without
registering the property, it&#39;ll jump from one color to the other Because,
without being registered, the browser doesn&#39;t know what to expect between one
value and the next and therefore can&#39;t guarantee the ability to transition them.
This example can be taken a step further, though, to animate CSS gradients! The
following CSS can be written with the same registered property:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;--stop-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--stop-color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; black&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; --stop-color 1s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button:hover&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;--stop-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; green&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;This will animate our custom property that&#39;s part of the &lt;code&gt;linear-gradient&lt;/code&gt;, thus
animating our linear gradient. Check out the Glitch below to see the full code
in action and play around with it yourself.&lt;/p&gt;
&lt;!-- Copy and Paste Me --&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
&lt;iframe allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media&quot; src=&quot;https://glitch.com/embed/#!/embed/houdini-props-and-vals?path=style.css&amp;previewSize=40&amp;attributionHidden=true&quot; alt=&quot;houdini-props-and-vals on Glitch&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Houdini &lt;a href=&quot;http://ishoudinireadyyet.com/&quot;&gt;is on its way&lt;/a&gt; to browsers, and with it,
entirely new ways of working with and extending CSS. With the &lt;a href=&quot;https://developers.google.com/web/updates/2018/01/paintapi&quot;&gt;Paint
API&lt;/a&gt; already shipped
and now Custom Props and Vals, our creative toolbox is expanding, allowing us to
define typed CSS properties and use them to create and animate new and exciting
designs. There&#39;s more on the way, too, in the &lt;a href=&quot;https://github.com/w3c/css-houdini-drafts/issues&quot;&gt;Houdini issue
queue&lt;/a&gt; where you can give
feedback and see what&#39;s next for Houdini. Houdini exists to develop features
that explain the &amp;quot;magic&amp;quot; of styling and layout on the web, so get out there and
put those magical features to good use.&lt;/p&gt;
&lt;!--lint disable no-literal-urls--&gt;
&lt;p&gt;&lt;em&gt;Photo by
&lt;a href=&quot;https://unsplash.com/@der_maik_?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Maik Jonietz&lt;/a&gt;
on
&lt;a href=&quot;https://unsplash.com/search/photos/code?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
<author>
<name>Sam Richard</name>
</author>
</entry>
<entry>
<title>Prefetch resources to speed up future navigations</title>
<link href="https://web.dev/link-prefetch/"/>
<updated>2019-09-11T17:00:00-07:00</updated>
<id>https://web.dev/link-prefetch/</id>
<content type="html">&lt;p&gt;Research shows that &lt;a href=&quot;https://wpostats.com/&quot;&gt;faster load times result in higher conversion rates&lt;/a&gt; and better user experiences. If you have insight into how users move through your website and which pages they will likely visit next, you can improve load times of future navigations by downloading the resources for those pages ahead of time.&lt;/p&gt;
&lt;p&gt;This guide explains how to achieve that with &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt;, a &lt;a href=&quot;https://www.w3.org/TR/resource-hints/&quot;&gt;resource hint&lt;/a&gt; that enables you to implement prefetching in an easy and efficient way.&lt;/p&gt;
&lt;h2 id=&quot;improve-navigations-with-relprefetch&quot;&gt;Improve navigations with &lt;code&gt;rel=prefetch&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#improve-navigations-with-relprefetch&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adding &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; to a web page tells the browser to download entire pages, or some of the resources (like scripts or CSS files), that the user might need in the future. This can improve metrics like &lt;a href=&quot;https://web.dev/first-contentful-paint&quot;&gt;First Contentful Paint&lt;/a&gt; and &lt;a href=&quot;https://web.dev/interactive/&quot;&gt;Time to Interactive&lt;/a&gt; and can often make subsequent navigations appear to load instantly.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;prefetch&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;index.html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;&lt;img src=&quot;https://web.dev/link-prefetch/prefetch.png&quot; alt=&quot;A diagram showing how link prefetch works.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;prefetch&lt;/code&gt; hint consumes extra bytes for resources that are not immediately needed, so this technique needs to be applied thoughtfully; only prefetch resources when you are confident that users will need them. Consider not prefetching when users are on slow connections. You can detect that with the &lt;a href=&quot;https://web.dev/adaptive-serving-based-on-network-quality/&quot;&gt;Network Information API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are different ways to determine which links to prefetch. The simplest one is to prefetch the first link or the first few links on the current page. There are also libraries that use more sophisticated approaches, explained later in this post.&lt;/p&gt;
&lt;h2 id=&quot;use-cases&quot;&gt;Use cases &lt;a class=&quot;w-headline-link&quot; href=&quot;#use-cases&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;prefetching-subsequent-pages&quot;&gt;Prefetching subsequent pages &lt;a class=&quot;w-headline-link&quot; href=&quot;#prefetching-subsequent-pages&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prefetch HTML documents when subsequent pages are predictable, so that when a link is clicked, the page is loaded instantly.&lt;/p&gt;
&lt;p&gt;For example, in a product listing page, you can prefetch the page for the most popular product in the list. In some cases, the next navigation is even easier to anticipate—on a shopping cart page, the likelihood of a user visiting the checkout page is usually high which makes it a good candidate for prefetching.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;eBay implemented prefetching for the first five results on a search page to speed up future pages loads and saw a positive impact on conversion rates.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;prefetching-static-assets&quot;&gt;Prefetching static assets &lt;a class=&quot;w-headline-link&quot; href=&quot;#prefetching-static-assets&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prefetch static assets, like scripts or stylesheets, when subsequent sections the user might visit can be predicted. This is especially useful when those assets are shared across many pages.&lt;/p&gt;
&lt;p&gt;For example, Netflix takes advantage of the time users spend on logged-out pages, to prefetch React, which will be used once users log in. Thanks to this, they &lt;a href=&quot;https://medium.com/dev-channel/a-netflix-web-performance-case-study-c0bcde26a9d9&quot;&gt;reduced Time to Interactive by 30% for future navigations&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--gotchas&quot;&gt;
&lt;strong&gt;Gotchas!&lt;/strong&gt; &lt;p&gt;At the time of this writing, it is possible to share prefetched resources among pages served from different origins. When &lt;a href=&quot;https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/6KKXv1PqPZ0/oguPntMGDgAJ&quot;&gt;Double-keyed HTTP cache&lt;/a&gt; ships, this will only work for top-level navigations and same-origin subresources, but it won&#39;t be possible to reuse prefetched subresources among different origins. This means that, if &lt;code&gt;a.com&lt;/code&gt; prefetches the resource &lt;code&gt;b.com/library.js&lt;/code&gt;, it won&#39;t be available in &lt;code&gt;c.com&lt;/code&gt; cache. Some browsers, such as WebKit-based ones, already &lt;a href=&quot;https://webkit.org/blog/7675/intelligent-tracking-prevention/&quot;&gt;partition caches and HTML5 storage&lt;/a&gt; for all third-party domains.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;prefetching-on-demand-javascript-chunks&quot;&gt;Prefetching on-demand JavaScript chunks &lt;a class=&quot;w-headline-link&quot; href=&quot;#prefetching-on-demand-javascript-chunks&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/reduce-javascript-payloads-with-code-splitting&quot;&gt;Code-splitting&lt;/a&gt; your JavaScript bundles allows you to initially load only parts of an app and lazy-load the rest. If you&#39;re using this technique, you can apply prefetch to routes or components that are not immediately necessary but will likely be requested soon.&lt;/p&gt;
&lt;p&gt;For example, if you have a page that contains a button that opens a dialog box which contains an emoji picker, you can divide it into three JavaScript chunks—home, dialog and picker. Home and dialog could be initially loaded, while the picker could be loaded on-demand. Tools like webpack allow you to instruct the browser to prefetch these on-demand chunks.&lt;/p&gt;
&lt;h2 id=&quot;how-to-implement-relprefetch&quot;&gt;How to implement &lt;code&gt;rel=prefetch&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-to-implement-relprefetch&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The simplest way to implement &lt;code&gt;prefetch&lt;/code&gt; is adding a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the document:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;prefetch&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;index.html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The &lt;code&gt;as&lt;/code&gt; attribute is not mandatory, but it&#39;s recommended. It helps the browser set the right headers, and determine whether the resource is already in the cache. Example values for this attribute include: &lt;code&gt;document&lt;/code&gt;, &lt;code&gt;script&lt;/code&gt;, &lt;code&gt;style&lt;/code&gt;, &lt;code&gt;font&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#Attributes&quot;&gt;others&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also initiate prefetching via the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link&quot;&gt;&lt;code&gt;Link&lt;/code&gt; HTTP header&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Link: &amp;lt;/css/style.css&amp;gt;; rel=prefetch&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A benefit of specifying a prefetch hint in the HTTP Header is that the browser doesn&#39;t need to parse the document to find the resource hint, which can offer small improvements in some cases.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;&lt;code&gt;prefetch&lt;/code&gt; is supported in &lt;a href=&quot;https://caniuse.com/#search=prefetch&quot;&gt;all modern browsers except Safari&lt;/a&gt;. You can implement a fallback technique for Safari with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest&quot;&gt;XHR&lt;/a&gt; requests or the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;prefetching-javascript-modules-with-webpack-magic-comments&quot;&gt;Prefetching JavaScript modules with webpack magic comments &lt;a class=&quot;w-headline-link&quot; href=&quot;#prefetching-javascript-modules-with-webpack-magic-comments&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;webpack enables you to prefetch scripts for routes or functionality you&#39;re reasonably certain users will visit or use soon.&lt;/p&gt;
&lt;p&gt;The following code snippet lazy-loads a sorting functionality from the &lt;a href=&quot;https://lodash.com/&quot;&gt;lodash&lt;/a&gt; library to sort a group of numbers that will be submitted by a form:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;lodash.sortby&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sortInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Instead of waiting for the &#39;submit&#39; event to take place to load this functionality, you can prefetch this resource to increase the chances of having it available in the cache by the time the user submits the form. webpack allows that using the &lt;a href=&quot;https://webpack.js.org/api/module-methods/#magic-comments&quot;&gt;magic comments&lt;/a&gt; inside &lt;code&gt;import()&lt;/code&gt;:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* webpackPrefetch: true */&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;lodash.sortby&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/mark&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sortInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;This tells webpack to inject the &lt;code&gt;&amp;lt;link rel=”prefetch”&amp;gt;&lt;/code&gt; tag into the HTML document:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;prefetch&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;”script”&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;”1.bundle.js”&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;smart-prefetching-with-quicklink-and-guess.js&quot;&gt;Smart prefetching with quicklink and Guess.js &lt;a class=&quot;w-headline-link&quot; href=&quot;#smart-prefetching-with-quicklink-and-guess.js&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can also implement smarter prefetching with libraries that use &lt;code&gt;prefetch&lt;/code&gt; under the hood:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/quicklink&quot;&gt;quicklink&lt;/a&gt; uses &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API&quot;&gt;Intersection Observer API&lt;/a&gt; to detect when links come into the viewport and prefetches linked resources during &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback&quot;&gt;idle time&lt;/a&gt;. Bonus: quicklink weighs less than 1 KB!&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/guess-js&quot;&gt;Guess.js&lt;/a&gt; uses analytics reports to build a predictive model that is used to &lt;a href=&quot;https://web.dev/predictive-prefetching/&quot;&gt;smartly prefetch&lt;/a&gt; only what the user is likely to need.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both quicklink and Guess.js use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API&quot;&gt;Network Information API&lt;/a&gt; to avoid prefetching if a user is on a slow network or has &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Save-Data&quot;&gt;&lt;code&gt;Save-Data&lt;/code&gt;&lt;/a&gt; turned on.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;A wine company Jabong implemented prefetching with quicklink and achieved 2.7 s faster Time To Interactive on future pages.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;prefetching-under-the-hood&quot;&gt;Prefetching under the hood &lt;a class=&quot;w-headline-link&quot; href=&quot;#prefetching-under-the-hood&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Resource hints are not mandatory instructions and it&#39;s up to the browser to decide if, and when, they get executed.&lt;/p&gt;
&lt;p&gt;You can use prefetch multiple times in the same page. The browser queues up all hints and requests each resource when it&#39;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ#How_is_browser_idle_time_determined.3F&quot;&gt;idle&lt;/a&gt;. In Chrome, if a prefetch has not finished loading and the user navigates to the destined prefetch resource, the in-flight load is picked up as the navigation by the browser (other browser vendors might implement this differently).&lt;/p&gt;
&lt;p&gt;Prefetching takes place at the &lt;a href=&quot;https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc/edit&quot;&gt;&#39;Lowest&#39; priority&lt;/a&gt;, so prefetched resources do not compete for bandwidth with the resources required in the current page.&lt;/p&gt;
&lt;p&gt;Prefetched files are stored in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching&quot;&gt;HTTP Cache&lt;/a&gt;, or the &lt;a href=&quot;https://calendar.perfplanet.com/2016/a-tale-of-four-caches/&quot;&gt;memory cache&lt;/a&gt; (depending on whether the resource is cacheable or not), for an amount of time that varies by browsers. For example, in Chrome resources are kept around for five minutes, after which the normal cache-control rules for the resource apply.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;prefetch&lt;/code&gt; can greatly improve load times of future navigations and even make pages appear to load instantly. &lt;code&gt;prefetch&lt;/code&gt; is widely supported in modern browsers, which makes it an attractive technique to improve the navigation experience for many users. This technique requires loading extra bytes that might not be used, so be mindful when you use it; only do it when necessary, and ideally, only on fast networks.&lt;/p&gt;
</content>
<author>
<name>Demian Renzulli</name>
</author>
</entry>
<entry>
<title>Going beyond images with basic video for the web</title>
<link href="https://web.dev/video-basics/"/>
<updated>2019-09-02T17:00:00-07:00</updated>
<id>https://web.dev/video-basics/</id>
<content type="html">&lt;p&gt;Are you thinking about adding video to your website? As devices and network
connections have become faster and more powerful, you can move beyond images and
add video to your toolchest of techniques to build the web.
&lt;a href=&quot;https://www.foodbloggerpro.com/blog/how-we-improved-our-landing-page-conversion-rate-by-138/&quot;&gt;Research
shows&lt;/a&gt;
that websites with video lead to higher engagement and sales. So even if you
haven&#39;t added video to your sites yet, it&#39;s probably just a matter of time
until you do.&lt;/p&gt;
&lt;p&gt;In all likelihood, the video files you add to your site will be the largest
files that are downloaded. For that reason, it&#39;s extremely important to ensure
that the files are built for fast and steady playback to all of your customers.
Even though video can increase engagement and customer satisfaction, a video
that doesn&#39;t play or stalls during playback can lead to customer frustration.
This post focuses on using the HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag for delivering video, and
therefore will not cover streaming video.&lt;/p&gt;
&lt;p&gt;So let&#39;s get started!&lt;/p&gt;
&lt;h2 id=&quot;the-lessvideogreater-tag&quot;&gt;The &amp;lt;video&amp;gt; tag &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-lessvideogreater-tag&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It seems obvious, right? To add video, you have to add the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag, point to a
source, and then you&#39;re off to the races!&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;myVideo.mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;And, you&#39;re right. At the highest level, this is all you need to add a video to
the web. But there are a lot of attributes that you can add to the video tag to
improve the layout and delivery of the video.&lt;/p&gt;
&lt;h2 id=&quot;the-lesssourcegreater-tag&quot;&gt;The &amp;lt;source&amp;gt; tag &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-lesssourcegreater-tag&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Perhaps the best way to improve the delivery of video on the web is to optimize
the files that are delivered to the browser. The way to do this is using the
&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; tag:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;myWebmVideo.webm&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;video/webm&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;myh265Video.mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;video/mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;myh264Video.mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;video/mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;video&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;This references three separate source files. The browser starts at the top, and
picks the first format and codec that it can use. In the video world, the file
format, usually called the container, can be saved with different codecs, each
with different attributes. (&lt;a href=&quot;https://developers.google.com/web/fundamentals/media/manipulating/applications&quot;&gt;More on this
here&lt;/a&gt;.)
In the example above, the first choice is the WebM format (&lt;a href=&quot;https://www.webmproject.org/about/&quot;&gt;which can be encoded
with VP8 or VP9 codecs&lt;/a&gt;), and is supported
(at the time of writing) by 78% of &lt;a href=&quot;https://caniuse.com/#search=webm&quot;&gt;global
users&lt;/a&gt;. The second choice is the the H.265
codec of mp4, which is supported on &lt;a href=&quot;https://caniuse.com/#search=h265&quot;&gt;iOS and newer
Macs&lt;/a&gt;. These codecs are newer and have
improved data compression, while delivering the same quality video as older
video formats.&lt;/p&gt;
&lt;p&gt;The final choice in our list is H.264 mp4, which boasts support on 92% of all
&lt;a href=&quot;https://caniuse.com/#search=h264&quot;&gt;global
users&lt;/a&gt;,
but is an older format, and as such, is generally a lot larger than WebM or H.265
videos. In one example, you can see the difference for a two minute movie:&lt;/p&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;File size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;VP8&lt;/th&gt;
&lt;th&gt;5.5 MB&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;VP9&lt;/th&gt;
&lt;th&gt;4.2 MB&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;H.265&lt;/th&gt;
&lt;th&gt;5.4 MB&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;H.264&lt;/th&gt;
&lt;th&gt;16.1 MB&lt;/th&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Delivering files that are smaller is the best performance optimization you can
make to better deliver your videos. When a smaller video is downloaded, video
playback occurs sooner, and the video buffer fills up faster. This leads to
fewer stalls during video playback. Additionally, server load is
decreased, which makes up for the increased storage requirements of multiple
video files.&lt;/p&gt;
&lt;h2 id=&quot;the-preload-attribute&quot;&gt;The preload attribute &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-preload-attribute&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Videos cannot begin playback until there is some video downloaded and stored
locally. Using the preload attribute, you can control how much video is
downloaded on page load. There are three values for the preload attribute:
&lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt;, and &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;preload&#39;auto&#39;&quot;&gt;preload=&#39;auto&#39; &lt;a class=&quot;w-headline-link&quot; href=&quot;#preload&#39;auto&#39;&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;&#39;auto&#39;&lt;/code&gt; is used, the entire video will be downloaded, no matter if
the user presses play or not. This enables fast video playback as the video is
downloaded locally before the user presses play. From a data usage (and server
load perspective) this should only be used when it is highly probable that the
video is to be watched. Otherwise all the data of a full video download will be
wasted.&lt;/p&gt;
&lt;h3 id=&quot;preload&#39;metadata&#39;&quot;&gt;preload=&#39;metadata&#39; &lt;a class=&quot;w-headline-link&quot; href=&quot;#preload&#39;metadata&#39;&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is the default setting for preload on Chrome and Safari. When &lt;code&gt;&#39;metadata&#39;&lt;/code&gt;
is used, the first 3% of the video is downloaded. Though this shares caveats
with &lt;code&gt;&#39;auto&#39;&lt;/code&gt;, downloading just 3% of the video holds a much smaller server/data
usage cost than the entire video, while still ensuring a portion of the video is
stored locally for fast video startup.&lt;/p&gt;
&lt;h3 id=&quot;preload&#39;none&#39;&quot;&gt;preload=&#39;none&#39; &lt;a class=&quot;w-headline-link&quot; href=&quot;#preload&#39;none&#39;&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This saves the most data, but will lead to slower video startup when play is
pressed, because as the setting states, zero kilobytes of the video is
preloaded locally on the device. For videos that are present, but unlikely to
be played, this is the appropriate setting. This might also be used if the user
has enabled &lt;a href=&quot;https://blog.chromium.org/2019/04/data-saver-is-now-lite-mode.html&quot;&gt;Lite mode&lt;/a&gt; in their browser.&lt;/p&gt;
&lt;h2 id=&quot;poster&quot;&gt;poster &lt;a class=&quot;w-headline-link&quot; href=&quot;#poster&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You may want to have a poster image that displays over the video
window before the video starts playing:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;myVideo.mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;poster&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/image/myVideoImage.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/video-basics/no-poster.png&quot; alt=&quot;A video without a poster shows a black screen before it starts.&quot;&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-compare&quot;&gt;
&lt;p class=&quot;w-compare__label w-compare__label--worse&quot;&gt;
No poster image
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A video without a poster shows a black screen before it starts.
&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/video-basics/poster.png&quot; alt=&quot;A video with a poster is much more engaging.&quot;&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-compare&quot;&gt;
&lt;p class=&quot;w-compare__label w-compare__label--better&quot;&gt;
With a poster image
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A video with a poster is much more engaging.
&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;By adding a photo instead of a black box on the page, you make your website more
engaging and interactive. However, using the &lt;code&gt;poster&lt;/code&gt; attribute adds an image
download before the video download begins. For that reason, you might consider
avoiding adding a poster for videos that autoplay (as the additional download
will delay the video download).&lt;/p&gt;
&lt;h2 id=&quot;playback-controls&quot;&gt;Playback controls &lt;a class=&quot;w-headline-link&quot; href=&quot;#playback-controls&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adding a &lt;code&gt;controls&lt;/code&gt; attribute adds playback controls. Without these, your
customers cannot start or stop your video. You should add this for videos so
that users can stop and pause, change the volume, and so on. For background or
looping videos, you may wish to omit this attribute.&lt;/p&gt;
&lt;h2 id=&quot;muted&quot;&gt;muted &lt;a class=&quot;w-headline-link&quot; href=&quot;#muted&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;muted&lt;/code&gt; attribute causes playback to begin in a muted state. If no controls
are supplied, it will remain muted for the entirety of playback. If that is
intended it might make sense to remove the audio track from the video. This
further reduces the size of the video file being delivered to the customer.&lt;/p&gt;
&lt;p&gt;As with containers and codecs, removing the audio file, also called demuxing, is
also beyond the scope of this article. You can find instructions in the &lt;a href=&quot;https://developers.google.com/web/fundamentals/media/manipulating/cheatsheet#demux_split_audio_and_video&quot;&gt;Media
Manipulation Cheat
Sheet&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;loop&quot;&gt;loop &lt;a class=&quot;w-headline-link&quot; href=&quot;#loop&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To deliver a video that loops the content (like an animated GIF), add the &lt;code&gt;loop&lt;/code&gt;
attribute. As video files are typically much smaller than animated GIFs, this
mechanism allows you to &lt;a href=&quot;https://dougsillars.com/2017/04/12/animated-gifs-vs-video-files/&quot;&gt;replace your GIFs with video
files&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;autoplaying-video&quot;&gt;Autoplaying video &lt;a class=&quot;w-headline-link&quot; href=&quot;#autoplaying-video&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you want your video to play immediately (for example as a background video, or a
video that loops like an animated GIF) you can add the &lt;code&gt;autoplay&lt;/code&gt; attribute:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;myVideo.mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;autoplay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;That said, in order for a video to autoplay on mobile browsers, the &lt;code&gt;muted&lt;/code&gt;
attribute must also be added:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;myVideo.mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;autoplay&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;muted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Simply adding a video to your website will add a new realm of engagement for
your customers, but it is important that you deliver the content
properly—ensuring that the playback of the video is seamless and without
stalls. Using the built in attributes of the &amp;lt;video&amp;gt; tag can greatly help you
deliver flawless video to everyone who visits your website.&lt;/p&gt;
</content>
<author>
<name>Doug Sillars</name>
</author>
</entry>
<entry>
<title>Ready Player Web</title>
<link href="https://web.dev/ready-player-web/"/>
<updated>2019-08-20T17:00:00-07:00</updated>
<id>https://web.dev/ready-player-web/</id>
<content type="html">&lt;p&gt;Good game developers know that to capitalise on the opportunity of a particular platform it&#39;s important to embrace the unique characteristics of that platform. So what are the unique characteristics of the web? And what defines a web game?&lt;/p&gt;
&lt;p&gt;At Google I/O 2019 I presented my thoughts on the state of the web games ecosystem, the current best practices for modern web game development, and where the industry is heading. In this blog post, I&#39;ll summarise some of the key points from my talk which you can watch in full on YouTube:&lt;/p&gt;
&lt;div class=&quot;w-youtube&quot;&gt;
&lt;iframe class=&quot;w-youtube__embed&quot; src=&quot;https://www.youtube.com/embed/aVTYxHL45SA&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-challenges-of-web-games&quot;&gt;The challenges of web games &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-challenges-of-web-games&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before joining Google I created a mobile game known as &lt;a href=&quot;https://www.duetgame.com/&quot;&gt;Duet&lt;/a&gt; which was downloaded nearly 20 million times. Through that experience I learned that the three essential ingredients to building a successful business out of a game are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A functional game&lt;/li&gt;
&lt;li&gt;Users&lt;/li&gt;
&lt;li&gt;A way to monetize users&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without these three elements, a game developer cannot succeed. Nowadays, these last two points are the most critical. Closed HTML5 ecosystems such as WeChat, Facebook Instant Games, and more have demonstrated that building games using HTML5 is achievable.&lt;/p&gt;
&lt;h2 id=&quot;modern-best-practices&quot;&gt;Modern best practices &lt;a class=&quot;w-headline-link&quot; href=&quot;#modern-best-practices&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By &amp;quot;functional game&amp;quot; I refer to the three most core elements of what makes a game work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Visuals&lt;/li&gt;
&lt;li&gt;Audio&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In each of these areas, the web platform has made significant strides in the past few years. For CPU performance we have access to a &lt;a href=&quot;https://www.youtube.com/watch?v=njt-Qzw0mVY&quot;&gt;performant new standard called Web Assembly&lt;/a&gt;. From the graphics side, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API&quot;&gt;WebGL 1.0&lt;/a&gt; has &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API#WebGL_1&quot;&gt;good cross-browser support&lt;/a&gt; and future standards such as &lt;a href=&quot;https://www.youtube.com/watch?v=K2JzIUIHIhc&quot;&gt;WebGPU&lt;/a&gt; are positioning the web platform for an extensible future of graphics programming similar to Vulkan and Metal. Finally, for web audio we have the &lt;a href=&quot;https://www.youtube.com/watch?v=-GaD0RCp-Q0&quot;&gt;common Web Audio API and more recently the Audio Worklet API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recently, Unity previewed a new runtime called Project Tiny which is focused on building 2D games for HTML5-based platforms. Project Tiny applies a new modular design to the engine structure of Unity enabling the core Unity engine to be under 1 megabyte in size.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/ready-player-web/unity-tanks.gif&quot; alt=&quot;Two tanks engaged in a battle.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;Unity&#39;s Tanks Demo exported via HTML5.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;From the technical side, there has never been a better time to embrace web game development.&lt;/p&gt;
&lt;h2 id=&quot;enter-the-loop&quot;&gt;Enter the loop &lt;a class=&quot;w-headline-link&quot; href=&quot;#enter-the-loop&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A great game is obviously more than just good performance, graphics, and sound though–to be great a game must be fun.&lt;/p&gt;
&lt;p&gt;Fun is a difficult element to measure in a product. When a game is fun, interesting, or innovative enough, users will want to tell their friends–in other words, they&#39;ll want to share the experience. Tapping into this opportunity and coupling it with the web is a powerful combination that unlocks a lot of potential for viral growth. And on the web in particular, without a central discovery platform, our best bet towards acquiring users is to ensure our games are as viral as possible.&lt;/p&gt;
&lt;p&gt;Good game developers know that to capitalise on a particular platform–whether at a software or hardware level–it&#39;s important to embrace the unique characteristics of that platform. For example, if you&#39;re building a game for a console with motion controls, you should probably think about the best way to embrace those motion controls.&lt;/p&gt;
&lt;p&gt;In other words, you must respect the expectations of the users of the platform you&#39;re building for. What do users of the web expect? They expect web content to load fast and be interactive quickly. In my talk, I covered several examples of ways–both on and off the web–that games have been designed to load quickly, pull users into their game worlds, engage those users, and provide users with additional incentives to share their experiences.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/ready-player-web/minimalist-games.png&quot; alt=&quot;Three games with minimalist art styles.&quot;&gt;&lt;/p&gt;
&lt;p&gt;I personally believe that the key to building a successful web game is to lean into this unique characteristic of the web. Specifically, the strength of the web&#39;s URL structure and the sharing loop that users can join in.&lt;/p&gt;
&lt;p&gt;Here&#39;s an example of a web game I built using &lt;a href=&quot;http://construct.net/&quot;&gt;Construct 3&lt;/a&gt; that leverages the URL in a fun and engaging way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/ready-player-web/space-board.png&quot; alt=&quot;A level editor interface for a game.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://io-space-board.firebaseapp.com/&quot;&gt;Space Board&lt;/a&gt; is a very simple game that can be played on either mobile with touch controls or on desktop with keyboard input. The objective is to navigate a maze of obstacles to reach a goal at the end.&lt;/p&gt;
&lt;p&gt;How does Space Board leverage the URL in a unique fashion? By encoding the level structure into the URL itself. All levels are defined as a 10 by 10 grid of objects–e.g. walls, enemy turrets, keys, locked doors etc. The URL then lists all the individual grid positions and their contents. A wall is represented by a &lt;code&gt;W&lt;/code&gt; character. An empty space is an underscore character.&lt;/p&gt;
&lt;p&gt;Here&#39;s an example:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;https://io-space-board.firebaseapp.com/?gameWorld=_wwwwwwwwww___ww__eww_k__d___ww___ww___ww_wwwww_www_wwwww_www___ww___ww_s_ww_f_ww___ww___wwwwwwwwwwww&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;It&#39;s ugly but it does the job.&lt;/p&gt;
&lt;p&gt;Upon completing a level in Space Board, the player has the opportunity to design their own level using the simple level editor shown above. By enabling players to design their own levels we are giving them the opportunity for personalisation. When a user feels a connection to a game and a sense of ownership via creation and customisation they are more likely to want to share that &#39;thing&#39; with the world.&lt;/p&gt;
&lt;p&gt;The desire to share a game is the beginning of the viral loop that we are aiming to achieve with our web games. This game design and sharing mechanism is just one example that&#39;s possible but there are many other possibilities–I encourage you to watch my talk for further examples!&lt;/p&gt;
&lt;h2 id=&quot;return-on-investment&quot;&gt;Return on investment &lt;a class=&quot;w-headline-link&quot; href=&quot;#return-on-investment&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At present, there are ultimately two schools of thought with regards to how a game developer can generate revenue through web games:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monetizing the games directly&lt;/li&gt;
&lt;li&gt;Treating them as an acquisition channel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Treating web games as an acquisition channel means leveraging the web version of your native game as a mechanism to get your players hooked and convincing them to download your larger native binary. You then generate revenue with the native platform&#39;s built-in payment and billing backends.&lt;/p&gt;
&lt;p&gt;Monetization is usually a mixture of advertising and microtransactions. There is still work to be done for the web to compete with native mobile platforms in game advertising. For example, formats like Rewarded Video Ads have been extremely popular for native mobile games for several years and yet we&#39;re only now seeing ad networks deploy these formats on the web.&lt;/p&gt;
&lt;p&gt;Nonetheless, there are game developers who continue to be successful on the open web through advertising via traditional banner ads and interstitial video ads. Take a look at &lt;a href=&quot;https://support.google.com/adsense/answer/1705831&quot;&gt;Adsense for Games&lt;/a&gt; for more information on these formats.&lt;/p&gt;
&lt;p&gt;For microtransactions, the web offers complete flexibility due to the limitless number of payment methods that can be implemented. However this quality is a double-edged sword. The negative side of this is that players have less implicit trust towards a new website they discover versus the familiarity of the native mobile store payment methods.&lt;/p&gt;
&lt;p&gt;One solution that brings a more consistent payment UI to the web is the &lt;a href=&quot;https://developers.google.com/web/fundamentals/payments/&quot;&gt;Payment Request API&lt;/a&gt;. This API invokes a UI that is shown by the browser and streamlines the acquisition of payment details such as credit cards and billing addresses. However, acquiring payment details is just the first step of making a transaction. You need a backend billing platform as well.&lt;/p&gt;
&lt;h2 id=&quot;the-future&quot;&gt;The future &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-future&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ve seen several surprisingly successful web games over the past few years. Slither.io has built a mixed web and native business that demonstrates the tremendous reach and viral growth opportunity that the web offers. Portals such as &lt;a href=&quot;https://poki.com/&quot;&gt;Poki.com&lt;/a&gt; are innovating in their user experience and releasing new games every day including titles that match the fidelity of their mobile counterparts, such as Subway Surfers or Crossy Road.&lt;/p&gt;
&lt;p&gt;Furthermore, if you look outside of the open web you can see that web games are already taking off. Closed ecosystems such as WeChat and LINE offer satisfying games which aren&#39;t playable on the open web but which are built on top of web technologies like HTML5 and WebViews. This is a clear sign that the web has reached a level of fidelity that&#39;s capable of rivaling native mobile games–perhaps not in a textbook definition of fidelity but in a more important metric: player attention.&lt;/p&gt;
</content>
<author>
<name>Tom Greenaway</name>
</author>
</entry>
<entry>
<title>The Native File System API: Simplifying access to local files</title>
<link href="https://web.dev/native-file-system/"/>
<updated>2019-08-19T17:00:00-07:00</updated>
<id>https://web.dev/native-file-system/</id>
<content type="html">&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The Native File System API (formerly known as the Writeable Files API), is
available as an origin trial in Chrome 78 (beta in September,
stable in October) and later. It is part of
&lt;a href=&quot;https://developers.google.com/web/updates/capabilities&quot;&gt;capabilities project&lt;/a&gt;,
and this post will be updated as the implementation progresses.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;what-is-it&quot;&gt;What is the Native File System API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-is-it&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://wicg.github.io/native-file-system/&quot;&gt;Native File System API&lt;/a&gt; enables developers to build powerful web apps
that interact with files on the user&#39;s local device, like IDEs, photo and video
editors, text editors, and more. After a user grants a web app access, this
API allows web apps to read or save changes directly to files and folders
on the user&#39;s device.&lt;/p&gt;
&lt;p&gt;If you&#39;ve worked with reading and writing files before, much of what I&#39;m about
to share will be familliar to you. I encourage you to read anyway because not
all systems are alike.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--caution&quot;&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;
We&#39;ve put a lot of thought into the design and implementation of the Native
File System API to ensure that people can easily manage their files. See the
&lt;a href=&quot;#security-considerations&quot;&gt;security and permissions&lt;/a&gt; section
for more information.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;status&quot;&gt;Current status &lt;a class=&quot;w-headline-link&quot; href=&quot;#status&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Create explainer&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/WICG/native-file-system/blob/master/EXPLAINER.md&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Create initial draft of specification&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://wicg.github.io/native-file-system/&quot;&gt;In progress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Gather feedback &amp;amp; iterate on design&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://wicg.github.io/native-file-system/&quot;&gt;In progress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Origin trial&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;#origin-trial&quot;&gt;In progress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Launch&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;how-to-use&quot;&gt;Using the Native File System API &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-to-use&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To show off the true power and usefulness of the Native File System APIs,
I wrote a single file &lt;a href=&quot;https://googlechromelabs.github.io/text-editor/&quot;&gt;text editor&lt;/a&gt;. It lets you open a text
file, edit it, save the changes back to disk, or start a new file and save
the changes to disk. It&#39;s nothing fancy, but provides enough to help you
understand the concepts.&lt;/p&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it &lt;a class=&quot;w-headline-link&quot; href=&quot;#try-it&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;See the Native File System API in action in the
&lt;a href=&quot;https://googlechromelabs.github.io/text-editor/&quot;&gt;text editor&lt;/a&gt; demo.&lt;/p&gt;
&lt;h3 id=&quot;enabling-via-chrome:flags&quot;&gt;Enabling via chrome://flags &lt;a class=&quot;w-headline-link&quot; href=&quot;#enabling-via-chrome:flags&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you want to experiment with the Native File System API locally, enable
the &lt;code&gt;#native-file-system-api&lt;/code&gt; flag in &lt;code&gt;chrome://flags&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;origin-trial&quot;&gt;Enabling support during the origin trial phase &lt;a class=&quot;w-headline-link&quot; href=&quot;#origin-trial&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Starting in Chrome 78, the Native File System API is available as an
origin trial on all desktop platforms.&lt;/p&gt;
&lt;p&gt;Origin trials allow you to try new features and give feedback on their
usability, practicality, and effectiveness to the web standards community. For
more information, see the &lt;a href=&quot;https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md&quot;&gt;Origin Trials Guide for Web Developers&lt;/a&gt;.
To sign up for this or another origin trial, visit the &lt;a href=&quot;https://developers.chrome.com/origintrials/#/trials/active&quot;&gt;registration page&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/3868592079911256065&quot;&gt;Request a token&lt;/a&gt; for your origin.&lt;/li&gt;
&lt;li&gt;Add the token to your pages. There are two ways to do that:
&lt;ul&gt;
&lt;li&gt;Add an &lt;code&gt;origin-trial&lt;/code&gt; &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag to the head of each page. For example,
this may look something like: &lt;br&gt;
&lt;code&gt;&amp;lt;meta http-equiv=&amp;quot;origin-trial&amp;quot; content=&amp;quot;TOKEN_GOES_HERE&amp;quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If you can configure your server, you can also add the token
using an &lt;code&gt;Origin-Trial&lt;/code&gt; HTTP header. The resulting response header should
look something like:&lt;br&gt;
&lt;code&gt;Origin-Trial: TOKEN_GOES_HERE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;read-file&quot;&gt;Read a file from the local file system &lt;a class=&quot;w-headline-link&quot; href=&quot;#read-file&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first use case I wanted to tackle was to ask the user to choose a file,
then open and read that file from disk.&lt;/p&gt;
&lt;h4 id=&quot;ask-the-user-to-pick-a-file-to-read&quot;&gt;Ask the user to pick a file to read &lt;a class=&quot;w-headline-link&quot; href=&quot;#ask-the-user-to-pick-a-file-to-read&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The entry point to the Native File System API is
&lt;a href=&quot;https://wicg.github.io/native-file-system/#api-choosefilesystementries&quot;&gt;&lt;code&gt;window.chooseFileSystemEntries()&lt;/code&gt;&lt;/a&gt;. When called, it shows
a file picker dialog box, and prompts the user to select a file. After selecting
a file, the API returns a handle to the file. An optional options parameter
lets you influence the behavior of the file picker, for example, by allowing the
user to select multiple files, or directories, or different file types.
Without any options specified, the file picker allows the user to select a
single file. This is perfect for our text editor.&lt;/p&gt;
&lt;p&gt;Like many other powerful APIs, calling &lt;code&gt;chooseFileSystemEntries()&lt;/code&gt; must be
done in a &lt;a href=&quot;https://w3c.github.io/webappsec-secure-contexts/&quot;&gt;secure context&lt;/a&gt;, and must be called from within
a user gesture.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;butOpenFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseFileSystemEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Do something with the file handle&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Once the user selects a file, &lt;code&gt;chooseFileSystemEntries()&lt;/code&gt; returns a handle,
in this case a &lt;a href=&quot;https://wicg.github.io/native-file-system/#api-filesystemfilehandle&quot;&gt;&lt;code&gt;FileSystemFileHandle&lt;/code&gt;&lt;/a&gt; that contains the
properties and methods needed to interact with the file.&lt;/p&gt;
&lt;p&gt;It&#39;s helpful to keep a reference to the file handle around so that it can be
used later. It&#39;ll be needed to save changes back to the file, or to perform any
other file operations. In the next few versions of Chrome, installed Progressive
Web Apps will also be able to save the handle to IndexedDB and persist access to
the file across page reloads.&lt;/p&gt;
&lt;h4 id=&quot;read-a-file-from-the-file-system&quot;&gt;Read a file from the file system &lt;a class=&quot;w-headline-link&quot; href=&quot;#read-a-file-from-the-file-system&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Now that you have a handle to a file, you can get the file&#39;s properties, or
access the file itself. For now, let&#39;s simply read its contents. Calling
&lt;code&gt;handle.getFile()&lt;/code&gt; returns a &lt;a href=&quot;https://w3c.github.io/FileAPI/&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object, which contains
a blob. To get the data from the blob, call one of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Blob&quot;&gt;its
methods&lt;/a&gt; (&lt;code&gt;slice()&lt;/code&gt;, &lt;code&gt;stream()&lt;/code&gt;, &lt;code&gt;text()&lt;/code&gt;, &lt;code&gt;arrayBuffer()&lt;/code&gt;).&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h4 id=&quot;putting-it-all-together&quot;&gt;Putting it all together &lt;a class=&quot;w-headline-link&quot; href=&quot;#putting-it-all-together&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When users click the Open button, the browser
shows a file picker. Once they&#39;ve selected a file, the app reads the
contents and puts them into a &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;butOpenFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseFileSystemEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; textArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contents&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;write-file&quot;&gt;Write the file to the local file system &lt;a class=&quot;w-headline-link&quot; href=&quot;#write-file&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the text editor, there are two ways to save a file: Save, and Save As.
Save simply writes the changes back to the original file using the file
handle we got earlier. But Save As creates a new file, and thus requires a
new file handle.&lt;/p&gt;
&lt;h4 id=&quot;create-a-new-file&quot;&gt;Create a new file &lt;a class=&quot;w-headline-link&quot; href=&quot;#create-a-new-file&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Passing &lt;code&gt;{type: &#39;saveFile&#39;}&lt;/code&gt; to &lt;code&gt;chooseFileSystemEntries()&lt;/code&gt; will show the
file picker in &amp;quot;save&amp;quot; mode, allowing the user to pick a new file they want
to use for saving. For the text editor, I also wanted it to automatically
add a &lt;code&gt;.txt&lt;/code&gt; extension, so I provided some additional parameters.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNewFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; opts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;saveFile&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; accepts&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; description&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Text file&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; extensions&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; mimeTypes&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseFileSystemEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h4 id=&quot;save-changes-to-the-original-file&quot;&gt;Save changes to the original file &lt;a class=&quot;w-headline-link&quot; href=&quot;#save-changes-to-the-original-file&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can find all the code for saving changes to a file in my &lt;a href=&quot;https://googlechromelabs.github.io/text-editor/&quot;&gt;text
editor&lt;/a&gt; demo on &lt;a href=&quot;https://github.com/GoogleChromeLabs/text-editor/&quot;&gt;GitHub&lt;/a&gt;. The core file system
interactions are in &lt;a href=&quot;https://github.com/GoogleChromeLabs/text-editor/blob/master/src/inline-scripts/fs-helpers.js&quot;&gt;&lt;code&gt;fs-helpers.js&lt;/code&gt;&lt;/a&gt;. At its simpliest,
the process looks like the code below. I&#39;ll walk through each step and explain it.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fileHandle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Create a writer (request permission if necessary).&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWriter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Write the full length of the contents&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contents&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Close the file and write the contents to disk&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;To write data to disk, I needed a &lt;a href=&quot;https://wicg.github.io/native-file-system/#filesystemwriter&quot;&gt;&lt;code&gt;FileSystemWriter&lt;/code&gt;&lt;/a&gt;. Create one by
calling &lt;code&gt;createWriter()&lt;/code&gt; on the file handle object. When &lt;code&gt;createWriter()&lt;/code&gt; is
called, Chrome first checks if the user has granted write permission to the file.
If permission to write hasn&#39;t been granted, the browser will prompt the user for
permission. If permission isn&#39;t granted, &lt;code&gt;createWriter()&lt;/code&gt; will throw a
&lt;code&gt;DOMException&lt;/code&gt;, and the app will not be able to write to the file. In the text
editor, these &lt;code&gt;DOMException&lt;/code&gt;s are handled in the &lt;a href=&quot;https://github.com/GoogleChromeLabs/text-editor/blob/master/src/inline-scripts/app.js&quot;&gt;&lt;code&gt;saveFile()&lt;/code&gt;&lt;/a&gt;
method.&lt;/p&gt;
&lt;p&gt;Call &lt;code&gt;FileSystemWriter.write()&lt;/code&gt; to write your contents. The &lt;code&gt;write()&lt;/code&gt; method
takes a string, which is what we want for a text editor. But it can also take a
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/BufferSource&quot;&gt;BufferSource&lt;/a&gt;, or a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Blob&quot;&gt;Blob&lt;/a&gt;. Finally, finish writing by
calling &lt;code&gt;FileSystemWriter.close()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--caution&quot;&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;
There&#39;s no guarantee that the contents are written to disk until
the &lt;code&gt;close()&lt;/code&gt; method is called.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;what-else-is-possible&quot;&gt;What else is possible? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-else-is-possible&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Beyond reading and writing files, the Native File System API provides
several other new capabilities.&lt;/p&gt;
&lt;h4 id=&quot;open-a-directory-and-enumerate-its-contents&quot;&gt;Open a directory and enumerate its contents &lt;a class=&quot;w-headline-link&quot; href=&quot;#open-a-directory-and-enumerate-its-contents&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To enumerate all files in a directory, call &lt;code&gt;chooseFileSystemEntries()&lt;/code&gt;
with the &lt;code&gt;type&lt;/code&gt; option set to &lt;code&gt;&#39;openDirectory&#39;&lt;/code&gt;. The user selects a directory
in a picker, after which a &lt;a href=&quot;https://wicg.github.io/native-file-system/#api-filesystemdirectoryhandle&quot;&gt;&lt;code&gt;FileSystemDirectoryHandle&lt;/code&gt;&lt;/a&gt;
is returned, which lets you enumerate and access the directory&#39;s files.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; butDir &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;butDirectory&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;butDir&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; opts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;openDirectory&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseFileSystemEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isFile &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;File&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Directory&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kind&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;whats-supported&quot;&gt;What&#39;s currently supported? &lt;a class=&quot;w-headline-link&quot; href=&quot;#whats-supported&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re still working on some of the implementation for the
Native File System API, and not everything in the &lt;a href=&quot;https://wicg.github.io/native-file-system/&quot;&gt;spec&lt;/a&gt;
(or &lt;a href=&quot;https://github.com/WICG/native-file-system/blob/master/EXPLAINER.md&quot;&gt;explainer&lt;/a&gt;) has been completed.&lt;/p&gt;
&lt;p&gt;As of Chrome 78, the following functionality is not available, or
doesn&#39;t match the spec:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handles are not serializable, meaning they cannot be passed via
&lt;code&gt;postMessage()&lt;/code&gt;, or stored in IndexedDB.&lt;/li&gt;
&lt;li&gt;Non-atomic writes (i.e. calls to &lt;code&gt;FileSystemFileHandle.createWriter()&lt;/code&gt;
with &lt;code&gt;inPlace: true&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Writing to a file using a &lt;a href=&quot;https://streams.spec.whatwg.org/#ws-class&quot;&gt;&lt;code&gt;WritableStream&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://wicg.github.io/native-file-system/#api-filesystemdirectoryhandle&quot;&gt;&lt;code&gt;FileSystemDirectoryHandle.resolve()&lt;/code&gt;&lt;/a&gt; method.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;security-considerations&quot;&gt;Security and permissions &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-considerations&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chrome team has designed and implemented the Native File System API using
the core principles defined in
&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/lkgr/docs/security/permissions-for-powerful-web-platform-features.md&quot;&gt;Controlling Access to Powerful Web Platform Features&lt;/a&gt;,
including user control and transparency, and user ergonomics.&lt;/p&gt;
&lt;h3 id=&quot;opening-a-file-or-saving-a-new-file&quot;&gt;Opening a file or saving a new file &lt;a class=&quot;w-headline-link&quot; href=&quot;#opening-a-file-or-saving-a-new-file&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;a href=&quot;https://web.dev/native-file-system/fs-open.jpg&quot;&gt;
&lt;img src=&quot;https://web.dev/native-file-system/fs-open.jpg&quot; alt=&quot;File picker to open a file for reading&quot;&gt;
&lt;/a&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
A file picker used to open an existing file for reading.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When opening a file, the user provides permission to read a file or
directory via the file picker. The open file picker can only be shown via
a user gesture when served from a &lt;a href=&quot;https://w3c.github.io/webappsec-secure-contexts/&quot;&gt;secure context&lt;/a&gt;. If
users change their minds, they can cancel the selection in the file
picker and the site does not get access to anything. This is the same
behavior as that of the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;figure class=&quot;w-figure w-figure--inline-left&quot;&gt;
&lt;a href=&quot;https://web.dev/native-file-system/fs-save.jpg&quot;&gt;
&lt;img src=&quot;https://web.dev/native-file-system/fs-save.jpg&quot; alt=&quot;File picker to save a file to disk.&quot;&gt;
&lt;/a&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
A file picker used to save a file to disk.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Similarly, when a web app wants to save a new file, the browser will show
the save file picker, allowing the user to specify the name and location
of the new file. Since they are saving a new file to the device (versus
overwriting an existing file), the file picker grants the app permission
to write to the file.&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;h4 id=&quot;restricted-folders&quot;&gt;Restricted folders &lt;a class=&quot;w-headline-link&quot; href=&quot;#restricted-folders&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To help protect users and their data, the browser may limit the user&#39;s
ability to save to certain folders, for example, core operating system
folders like Windows, the macOS Library folders, etc. When this happens,
the browser will show a modal prompt and ask the user to choose a
different folder.&lt;/p&gt;
&lt;h3 id=&quot;modifying-an-existing-file-or-directory&quot;&gt;Modifying an existing file or directory &lt;a class=&quot;w-headline-link&quot; href=&quot;#modifying-an-existing-file-or-directory&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A web app cannot modify a file on disk without getting explicit permission
from the user.&lt;/p&gt;
&lt;h4 id=&quot;permission-prompt&quot;&gt;Permission prompt &lt;a class=&quot;w-headline-link&quot; href=&quot;#permission-prompt&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;a href=&quot;https://web.dev/native-file-system/fs-save-permission.jpg&quot;&gt;
&lt;img src=&quot;https://web.dev/native-file-system/fs-save-permission-crop.jpg&quot; class=&quot;w-screenshot&quot; alt=&quot;Permission prompt shown prior to saving a file.&quot;&gt;
&lt;/a&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Prompt shown to users before the browser is granted write
permission on an existing file.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If a person wants to save changes to a file that they previously granted
read access to, the browser will show a modal permission prompt, requesting
permission for the site to write changes to disk. The permission request
can only be triggered by a user gesture, for example, by clicking a &amp;quot;Save&amp;quot;
button.&lt;/p&gt;
&lt;p&gt;Alternatively, a web app that edits multiple files, like an IDE, can
also ask for permission to save changes at the time of opening.&lt;/p&gt;
&lt;p&gt;If the user chooses &lt;em&gt;Cancel&lt;/em&gt;, and does not grant write access, the web
app cannot save changes to the local file. It should provide an alternative
method to allow the user to save their data, for example by providing a way to
&lt;a href=&quot;https://web.dev/web/updates/2011/08/Downloading-resources-in-HTML5-a-download&quot;&gt;&amp;quot;download&amp;quot; the file&lt;/a&gt;, saving data to the cloud, etc.&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;h3 id=&quot;transparency&quot;&gt;Transparency &lt;a class=&quot;w-headline-link&quot; href=&quot;#transparency&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;a href=&quot;https://web.dev/native-file-system/fs-save-icon.jpg&quot;&gt;
&lt;img src=&quot;https://web.dev/native-file-system/fs-save-icon.jpg&quot; class=&quot;w-screenshot&quot; alt=&quot;Omnibox icon&quot;&gt;
&lt;/a&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Omnibox icon indicating the user has granted the website permission to
save to a local file.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once a user has granted permission to a web app to save a local file,
Chrome will show an icon in the omnibox. Clicking on the omnibox icon
opens a popover showing the list of files the user has given access to.
The user can easily revoke that access if they choose.&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;h3 id=&quot;permission-persistence&quot;&gt;Permission persistence &lt;a class=&quot;w-headline-link&quot; href=&quot;#permission-persistence&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The web app can continue to save changes to the file without prompting as long
as the tab is open. Once a tab is closed, the site loses all access. The next
time the user uses the web app, they will be re-prompted for access to the
files. In the next few versions of Chrome, installed Progressive Web Apps (only)
will also be able to save the handle to IndexedDB and persist access to handles
across page reloads. In this case, an icon will be shown in the omnibox as long
as the app has write access to local files.&lt;/p&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;w-headline-link&quot; href=&quot;#feedback&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We want to hear about your experiences with the Native File System API.&lt;/p&gt;
&lt;h3 class=&quot;hide-from-toc&quot; id=&quot;tell-us-about-the-api-design&quot;&gt;Tell us about the API design &lt;a class=&quot;w-headline-link&quot; href=&quot;#tell-us-about-the-api-design&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Is there something about the API that doesn&#39;t work like you expected? Or
are there missing methods or properties that you need to implement your
idea? Have a question or comment on the security model?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File a spec issue on the &lt;a href=&quot;https://github.com/wicg/native-file-system/issues/&quot;&gt;WICG Native File System GitHub repo&lt;/a&gt;,
or add your thoughts to an existing issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;hide-from-toc&quot; id=&quot;problem-with-the-implementation&quot;&gt;Problem with the implementation? &lt;a class=&quot;w-headline-link&quot; href=&quot;#problem-with-the-implementation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you find a bug with Chrome&#39;s implementation? Or is the implementation
different from the spec?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File a bug at &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/entry?components=Blink%3EStorage%3EFileSystem&quot;&gt;https://new.crbug.com&lt;/a&gt;. Be sure to include as
much detail as you can, simple instructions for reproducing, and set
&lt;em&gt;Components&lt;/em&gt; to &lt;code&gt;Blink&amp;gt;Storage&amp;gt;FileSystem&lt;/code&gt;. &lt;a href=&quot;https://glitch.com/&quot;&gt;Glitch&lt;/a&gt;
works great for sharing quick and easy repros.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class=&quot;hide-from-toc&quot; id=&quot;planning-to-use-the-api&quot;&gt;Planning to use the API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#planning-to-use-the-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Planning to use the Native File System API on your site? Your public support
helps us to prioritize features, and shows other browser vendors how
critical it is to support them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Share how you plan to use it on the &lt;a href=&quot;https://discourse.wicg.io/t/writable-file-api/1433&quot;&gt;WICG Discourse thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Send a Tweet to &lt;a href=&quot;https://twitter.com/chromiumdev&quot;&gt;@ChromiumDev&lt;/a&gt; with &lt;code&gt;#nativefs&lt;/code&gt; and
let us know where and how you&#39;re using it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;helpful&quot;&gt;Helpful links &lt;a class=&quot;w-headline-link&quot; href=&quot;#helpful&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/native-file-system/blob/master/EXPLAINER.md&quot;&gt;Public explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/native-file-system/&quot;&gt;Native File System specification&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://w3c.github.io/FileAPI/&quot;&gt;File specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crbug.com/853326&quot;&gt;Tracking bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/6284708426022912&quot;&gt;ChromeStatus.com entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Request an &lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/3868592079911256065&quot;&gt;origin trial token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/document/d/1NJFd-EWdUlQ7wVzjqcgXewqC5nzv_qII4OvlDtK6SE8/edit&quot;&gt;Native File System API - Chromium Security Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blink Component: &lt;code&gt;Blink&amp;gt;Storage&amp;gt;FileSystem&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
<author>
<name>Pete LePage</name>
</author>
</entry>
<entry>
<title>Progressive Web Apps in multi-origin sites</title>
<link href="https://web.dev/multi-origin-pwas/"/>
<updated>2019-08-18T17:00:00-07:00</updated>
<id>https://web.dev/multi-origin-pwas/</id>
<content type="html">&lt;h2 id=&quot;background&quot;&gt;Background &lt;a class=&quot;w-headline-link&quot; href=&quot;#background&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the past, there were some advantages to using multi-origin architectures, but for Progressive Web Apps, that approach presents many challenges. In particular, the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy&quot;&gt;same-origin policy&lt;/a&gt;, imposes restrictions for sharing things like service workers and caches, permissions, and for achieving a standalone experience across multiple origins. This article will describe the good and bad uses of multiple origins, and explain the challenges and workarounds for building Progressive Web Apps in multi-origin sites.&lt;/p&gt;
&lt;h2 id=&quot;good-and-bad-uses-of-multiple-origins&quot;&gt;Good and bad uses of multiple origins &lt;a class=&quot;w-headline-link&quot; href=&quot;#good-and-bad-uses-of-multiple-origins&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a few legitimate reasons for sites to employ a multi-origin architecture, mostly related to providing an independent set of web applications, or to create experiences that are completely isolated from each other. There are also uses that should be avoided.&lt;/p&gt;
&lt;h3 id=&quot;the-good&quot;&gt;The good &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-good&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&#39;s look at the useful reasons first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Localization / Language:&lt;/strong&gt; Using a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/TLD&quot;&gt;country-code top-level domain&lt;/a&gt;, to separate sites to be served in different countries (e.g. &lt;code&gt;https://www.google.com.ar&lt;/code&gt;), or using subdomains to divide sites targeted to different locations (e.g.: &lt;code&gt;https://newyork.craigslist.org&lt;/code&gt;) or to offer content for a specific language (e.g. &lt;code&gt;https://en.wikipedia.org&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Device/Platform:&lt;/strong&gt; Using different subdomains, to deliver different versions of a website to different devices. The &lt;code&gt;m.&lt;/code&gt; or &lt;code&gt;mobile.&lt;/code&gt; pattern is a common way to separate mobile from desktop in adaptive sites. For example: a site can maintain its desktop version at &lt;code&gt;https://www.example.com&lt;/code&gt; and mobile version at &lt;code&gt;https://m.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Independent webapps:&lt;/strong&gt; Using different subdomains to provide experiences whose purpose differs considerably from the site on the main origin. For example, in a news site, the crosswords webapp could be intentionally served from &lt;code&gt;https://crosswords.example.com&lt;/code&gt;, and installed and used as an independent PWA, without having to share any resources or functionality with the main website.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-bad&quot;&gt;The bad &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-bad&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re not doing any of these things, it&#39;s likely that using a multi-origin architecture will be a disadvantage when building Progressive Web Apps.&lt;/p&gt;
&lt;p&gt;Despite this, many sites continue being structured this way for no particular reason, or for &#39;legacy&#39; reasons. One example is using subdomains to arbitrarily separate parts of a site that should be part of a unified experience.&lt;/p&gt;
&lt;p&gt;The following patterns, for example, are highly discouraged:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Site sections:&lt;/strong&gt; Separating different sections of a site on subdomains. In news sites, it&#39;s not uncommon to see the home page at: &lt;code&gt;https://www.example.com&lt;/code&gt;, while the sports section lives at &lt;code&gt;https://sports.example.com&lt;/code&gt;, politics at &lt;code&gt;https://politics.example.com&lt;/code&gt;, and so forth. In the case of an e-commerce site, using something like &lt;code&gt;https://category.example.com&lt;/code&gt; for product categories, &lt;code&gt;https://product.example.com&lt;/code&gt; for product pages, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User Flow:&lt;/strong&gt; Another approach that&#39;s discouraged is to separate different smaller parts of the site, like pages for the login or purchase flows in subdomains. For example, using &lt;code&gt;https://login.example.com&lt;/code&gt;, and &lt;code&gt;https://checkout.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;When building a site from scratch it&#39;s highly recommended to avoid dividing it into subdomains. For existing sites, migrating to a single origin is the best approach.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;For those cases where migrating to a single origin is not possible, what follows is a list of challenges, and (where possible), workarounds that can be considered when building Progressive Web Apps.&lt;/p&gt;
&lt;h2 id=&quot;challenges-and-workarounds-for-pwas-across-different-origins&quot;&gt;Challenges and Workarounds for PWAs across different origins &lt;a class=&quot;w-headline-link&quot; href=&quot;#challenges-and-workarounds-for-pwas-across-different-origins&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When building a website on multiple origins, providing a unified PWA experience is challenging, mostly because of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy&quot;&gt;same-origin policy&lt;/a&gt;, which imposes a number of constraints. Let&#39;s look at them one at a time.&lt;/p&gt;
&lt;h3 id=&quot;service-workers&quot;&gt;Service workers &lt;a class=&quot;w-headline-link&quot; href=&quot;#service-workers&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The origin of the service worker script URL has to be the same as the origin of the page calling &lt;a href=&quot;https://w3c.github.io/ServiceWorker/#navigator-service-worker-register&quot;&gt;register()&lt;/a&gt;. This means that, for example, a page at &lt;code&gt;https://www.example.com&lt;/code&gt; can&#39;t call &lt;code&gt;register()&lt;/code&gt; with a service worker url at &lt;code&gt;https://section.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another consideration is that a service worker can only control pages hosted under the origin and path it belongs to. This means that, if the service worker is hosted at &lt;code&gt;https://www.example.com&lt;/code&gt; it can only control URLs from that origin (according to the path defined in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#Parameters&quot;&gt;scope parameter&lt;/a&gt;), but won&#39;t control any page in other subdomains such as, for example, those in &lt;code&gt;https://section.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this case, the only workaround is to use multiple service workers (one per origin).&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--caution&quot;&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;
Registering, and having multiple active service workers consumes additional resources (memory, CPU, etc.), so use your best judgement on how many active service workers a user will likely need to navigate across the site.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;caching&quot;&gt;Caching &lt;a class=&quot;w-headline-link&quot; href=&quot;#caching&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Cache object, indexedDB, and localStorage are also constrained to a single origin. This means it&#39;s not possible to access the caches that belong to &lt;code&gt;https://www.example.com&lt;/code&gt;, from, for example: &lt;code&gt;https://www.section.example.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here are some things you can do to manage caches properly in scenarios like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Leverage browser caching:&lt;/strong&gt; Using &lt;a href=&quot;https://webkit.org/blog/8090/workers-at-your-service/&quot;&gt;traditional browser caching best practices&lt;/a&gt; is always recommended. This technique provides the added benefit of reusing cached resources across origins, which can&#39;t be done with the service worker&#39;s cache. For best practices on how to use HTTP Cache with service workers, you can take a look at &lt;a href=&quot;https://jakearchibald.com/2016/caching-best-practices/#the-service-worker-the-http-cache-play-well-together-dont-make-them-fight&quot;&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep service worker installation lightweight:&lt;/strong&gt; If you are maintaining multiple service workers, avoid making users pay a big installation cost every time they navigate to a new origin. In other words: only pre-cache resources that are absolutely necessary.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;w-aside w-aside--gotchas&quot;&gt;
&lt;strong&gt;Gotchas!&lt;/strong&gt; &lt;p&gt;Once the service worker is active and running, the same-origin policy also restricts cross-origin requests made &lt;strong&gt;&lt;em&gt;inside&lt;/em&gt;&lt;/strong&gt; service workers. Fortunately this has a recommended workaround, which is to use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&quot;&gt;CORS&lt;/a&gt; (as explained &lt;a href=&quot;https://developers.google.com/web/ilt/pwa/working-with-the-fetch-api#cross-origin_requests&quot;&gt;here&lt;/a&gt;). Using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters&quot;&gt;no-cors mode&lt;/a&gt; when fetching resources inside the service worker is not recommended.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;permissions&quot;&gt;Permissions &lt;a class=&quot;w-headline-link&quot; href=&quot;#permissions&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Permissions are also scoped to origins. This means that if a user granted a given permission to the origin &lt;code&gt;https://maps.google.com&lt;/code&gt;, it won&#39;t carry over to other origins, like &lt;code&gt;https://www.google.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since there&#39;s no way to share permissions across origins, the only solution here is to ask for permission on each of subdomain where a given feature is required (e.g. location). For things like web push, you can maintain a cookie to track if the permission has been accepted by the user in another subdomain, to avoid requesting it again.&lt;/p&gt;
&lt;h3 id=&quot;installation&quot;&gt;Installation &lt;a class=&quot;w-headline-link&quot; href=&quot;#installation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To install a PWA, each origin must have its own manifest with a &lt;code&gt;start_url&lt;/code&gt; that&#39;s &lt;a href=&quot;https://w3c.github.io/manifest/#start_url-member&quot;&gt;relative to itself&lt;/a&gt;. This means that a user receiving the installation prompt on a given origin (i.e: &lt;code&gt;https://section.example.com&lt;/code&gt;) won&#39;t be able to install the PWA with a &lt;code&gt;start_url&lt;/code&gt; on a different one (i.e: &lt;code&gt;https://www.example.com&lt;/code&gt;).
In other words, users receiving the installation prompt in a subdomain will only be able to install PWAs for the subpages, not for the main URL of the app.&lt;/p&gt;
&lt;p&gt;A simple workaround is to use a&lt;code&gt;start_url&lt;/code&gt; with a redirect to the main origin. For example: subdomain &lt;code&gt;https://section.example.com&lt;/code&gt;, can define a &lt;code&gt;start_url&lt;/code&gt; of &lt;code&gt;https://section.example.com/pwa&lt;/code&gt;, containing a redirect to: &lt;code&gt;https://www.example.com&lt;/code&gt;, making the user always land at the home page.&lt;/p&gt;
&lt;p&gt;There&#39;s also the issue that the same user could receive multiple installation prompts when navigating the site, if each subdomain meets the &lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/#criteria&quot;&gt;installation criteria&lt;/a&gt;, and prompts the user to install the PWA.&lt;/p&gt;
&lt;p&gt;Mitigate this problem by applying any of the following techniques:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Showing the prompt on a single domain:&lt;/strong&gt; If the site has many subdomains that pass the installation criteria, the &lt;code&gt;beforeinstallprompt&lt;/code&gt; event could be used and &lt;code&gt;preventDefault()&lt;/code&gt; called (as explained &lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/#listen_for_beforeinstallprompt&quot;&gt;here&lt;/a&gt;), to prevent the prompt from appearing in unintended parts of the site (like subdomains), while continue to show it in other parts (e.g. the home page).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Showing the prompt once, across different subdomains:&lt;/strong&gt; It&#39;s also possible to let each subdomain show the installation prompt. (This can make sense if some subdomains are more frequently visited). In this case, it&#39;s important to avoid showing the prompt to a user multiple times. To that purpose, a cookie could be used, to track if the prompt has already been shown, and check for its existence when the &lt;code&gt;beforeinstallprompt&lt;/code&gt; event is triggered. If the cookie is present, it means that the user already received the prompt somewhere else, so &lt;code&gt;preventDefault()&lt;/code&gt; can be called, to avoid showing it again.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;standalone-mode&quot;&gt;Standalone Mode &lt;a class=&quot;w-headline-link&quot; href=&quot;#standalone-mode&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While navigating in a standalone window, the browser will behave differently when the user moves outside of the scope set by the PWA&#39;s manifest. The behavior depends on each browser version and vendor. For example, the latest Chrome versions open a &lt;a href=&quot;https://developer.chrome.com/multidevice/android/customtabs&quot;&gt;Chrome custom tab&lt;/a&gt;, when a user moves out of the scope in fullscreen mode.&lt;/p&gt;
&lt;p&gt;In most cases, there&#39;s no solution for this, but a workaround can be applied for small parts of the experience that are hosted in subdomains (for example: login workflows):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The new URL, &lt;code&gt;https://login.example.com&lt;/code&gt;, could open inside a full screen iframe.&lt;/li&gt;
&lt;li&gt;Once the task is completed inside the iframe (for example, the login process), &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage&quot;&gt;postMessage()&lt;/a&gt; can be used, to pass any resulting information from the iframe back to the parent page.&lt;/li&gt;
&lt;li&gt;As a final step, once the message is received by the main page, the listeners can be unregistered, and the iframe finally be removed from the DOM.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;w-aside w-aside--caution&quot;&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;
The previous technique can help mitigating the potential UI change in a small part of the site, where the user can perform an action in a subdomain and return to the main origin (like in a login flow), but won&#39;t be an efficient technique to implement for entire paths, including many pages hosted in subdomains (like entire site sections).&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Same-origin policy imposes many restrictions for sites built on top of multiple origins that want to achieve a coherent PWA experience. For that reason, to provide the best experience to users, we strongly recommend against dividing sites into different origins.&lt;/p&gt;
&lt;p&gt;For existing sites that are already built in this way, it can be challenging to make multi-origin PWAs work correctly, but we have explored some potential workarounds. Each can come with tradeoffs, so use your judgement when deciding which approach to take on your website.&lt;/p&gt;
&lt;p&gt;When evaluating a long-term strategy or site redesign, consider migrating to a single origin, unless there&#39;s an important reason to keep the multi-origin architecture.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;With many thanks for their technical reviews and suggestions: PJ Mclachlan, Paul Covell, Dominick Ng, Alberto Medina, Pete LePage, Joe Medley, Cheney Tsai, Martin Schierle, and Andre Bandarra.&lt;/em&gt;&lt;/p&gt;
</content>
<author>
<name>Demian Renzulli</name>
</author>
</entry>
<entry>
<title>How Truebil made the web its channel of growth</title>
<link href="https://web.dev/truebil-lite/"/>
<updated>2019-08-15T17:00:00-07:00</updated>
<id>https://web.dev/truebil-lite/</id>
<content type="html">&lt;h2 id=&quot;about&quot;&gt;About &lt;a class=&quot;w-headline-link&quot; href=&quot;#about&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Founded in 2015, Truebil is an Indian online marketplace that sells 100% certified used cars. With over 1.4 million monthly active users, it&#39;s a one-stop solution that includes title transfer, insurance, loans, and service warranties. Prospective customers can see individual product pages with images and detailed inspection reports and get vehicle evaluations with the site&#39;s &amp;quot;Compare&amp;quot; and &amp;quot;Truescore&amp;quot; features. Truebil differentiates its product with rich features, including personalized recommendations based on machine learning, an add-to-favorites feature, a share-a-car feature, and more.&lt;/p&gt;
&lt;h2 id=&quot;challenge&quot;&gt;Challenge &lt;a class=&quot;w-headline-link&quot; href=&quot;#challenge&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Truebil is a lean startup with low-frequency, high-value transactions, so it was critical to choose the right platform to prioritize and invest in.&lt;/p&gt;
&lt;p&gt;Truebil identified mobile as their target platform, and they chose the web for their first app, &lt;a href=&quot;https://m.truebil.com/&quot;&gt;Truebil Lite&lt;/a&gt;, because of the web&#39;s easy discovery and low friction. Web technology provides lower development costs, less data and memory usage, and significantly lower customer acquisition costs than building a native app. And by building a progressive web app (PWA), Truebil could get all the perks of the web &lt;em&gt;and&lt;/em&gt; the benefits of native.&lt;/p&gt;
&lt;h2 id=&quot;solution&quot;&gt;Solution &lt;a class=&quot;w-headline-link&quot; href=&quot;#solution&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An in-house team took four months to develop Truebil Lite using React, Django, and Preact (for production migration). They set clear guiding principles for the web app based on user goals. The experience had to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast&lt;/strong&gt; on first load and subsequent navigations,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reliable&lt;/strong&gt;, independent of the user&#39;s network or device constraints, and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engaging&lt;/strong&gt;, especially for small mobile screens, so users would want to return to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;optimize-for-fast-first-load-and-navigations&quot;&gt;Optimize for fast first load and navigations &lt;a class=&quot;w-headline-link&quot; href=&quot;#optimize-for-fast-first-load-and-navigations&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/&quot;&gt;Lighthouse&lt;/a&gt; to guide performance optimizations, the team adopted a performance-first culture while implementing new features. Truebil was able to significantly improve user experience by prioritizing the &lt;a href=&quot;https://web.dev/first-contentful-paint&quot;&gt;First Contentful Paint&lt;/a&gt; and &lt;a href=&quot;https://web.dev/interactive&quot;&gt;Time to Interactive (TTI)&lt;/a&gt; metrics and optimizing for fast first loads, repeat visits, and smooth navigation. The team achieved those results by setting performance budgets and using a variety of techniques to achieve them.&lt;/p&gt;
&lt;h4 id=&quot;set-performance-budgets&quot;&gt;Set performance budgets &lt;a class=&quot;w-headline-link&quot; href=&quot;#set-performance-budgets&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;With a performance-first mindset, the Truebil team chose to architect their experience as a single page app with server-side rendering for first load and client-side rendering for subsequent loads. Keeping web apps with client side rendering performant can be difficult, so Truebil set very strict &lt;a href=&quot;https://web.dev/fast#set-performance-budgets&quot;&gt;performance budgets&lt;/a&gt; to ensure they don&#39;t compromise on speed, especially as they add more features.&lt;/p&gt;
&lt;p&gt;The team set strict milestone-based budgets for TTI with the goal of keeping it below five seconds. To meet that goal they manually ensured no build would exceed a 250 KB JavaScript bundle size, kept a constant check on image sizes, and continually tracked the app&#39;s Lighthouse performance score.&lt;/p&gt;
&lt;h4 id=&quot;optimize-javascript-bundles&quot;&gt;Optimize JavaScript bundles &lt;a class=&quot;w-headline-link&quot; href=&quot;#optimize-javascript-bundles&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The team started with the basics by using the &lt;a href=&quot;https://web.dev/apply-instant-loading-with-prpl&quot;&gt;PRPL pattern&lt;/a&gt; to precache and optimize JavaScript payloads and by moving to HTTP/2 to serve critical JavaScript bundles.&lt;/p&gt;
&lt;p&gt;To lazy-load non-critical resources, they used their framework-level lazy-loading components to load below-the-fold fragments.&lt;/p&gt;
&lt;p&gt;To remove any JavaScript bundle bottlenecks, the team &lt;a href=&quot;https://web.dev/reduce-javascript-payloads-with-code-splitting&quot;&gt;reduced payloads via code splitting&lt;/a&gt;. They used component- and route-based chunking to to reduce main bundle size and &lt;strong&gt;improve their loading time by 44%,&lt;/strong&gt; with TTI falling from 6 seconds to about 5 seconds and &lt;a href=&quot;https://web.dev/first-meaningful-paint&quot;&gt;First Meaningful Paint (FMP)&lt;/a&gt; from 4.1 seconds to 3.6 seconds.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/truebil-lite/chunking.png&quot; alt=&quot;Screenshots of Chrome DevTools showing Truebil Lite&#39;s build size before and after code splitting.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Impact of reducing chunk size.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;inline-critical-css&quot;&gt;Inline critical CSS &lt;a class=&quot;w-headline-link&quot; href=&quot;#inline-critical-css&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To further improve FMP, the team used Lighthouse to find opportunities for and validate the impact of performance optimizations. Lighthouse indicated that reducing render blocking CSS would have the biggest effect, so Truebil inlined all critical CSS and &lt;a href=&quot;https://web.dev/defer-non-critical-css&quot;&gt;deferred non-critical CSS&lt;/a&gt;. This technique &lt;strong&gt;reduced FMP by around 2 seconds&lt;/strong&gt;.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/truebil-lite/first-meaningful-paint.png&quot; alt=&quot;Screenshots of Chrome DevTools showing Truebil Lite&#39;s time to First Meaningful Paint before and after inlining CSS.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Impact of inlining critical CSS.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;avoid-multiple-costly-round-trips-to-any-origin&quot;&gt;Avoid multiple, costly round trips to any origin &lt;a class=&quot;w-headline-link&quot; href=&quot;#avoid-multiple-costly-round-trips-to-any-origin&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To mitigate overhead from DNS and TLS, Truebil used &lt;a href=&quot;https://web.dev/uses-rel-preconnect&quot;&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;preconnect&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;&amp;lt;link rel=&amp;quot;dns-prefetch&amp;quot;&amp;gt;&lt;/code&gt;. This approach causes the browser to complete the TLS handshake as soon as possible on page load and pre-resolve cross-origin domain names, allowing for a secure, snappy user experience.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/truebil-lite/preconnect.png&quot; alt=&quot;Screenshots of Chrome DevTools showing the effect of rel=preconnect.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Impact of adding &lt;code&gt;&amp;#60;link rel=preconnect&amp;#62;&lt;/code&gt;.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;dynamically-prefetch-the-next-page&quot;&gt;Dynamically prefetch the next page &lt;a class=&quot;w-headline-link&quot; href=&quot;#dynamically-prefetch-the-next-page&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;By analyzing their data, the team identified the most common user journeys that they could optimize for. In these cases, the app dynamically downloads the next page resource by using &lt;code&gt;&amp;lt;link rel=prefetch&amp;gt;&lt;/code&gt; to ensure smooth navigation for users. While the team manually identifies the links to prefetch, they use webpack to bundle the JS for those links.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img class=&quot;w-screenshot&quot; src=&quot;https://web.dev/truebil-lite/prefetch.png&quot; alt=&quot;Screenshots of the Truebil Lit app and Chrome DevTools showing that network requests aren&#39;t needed on common navigations because the assets have already been prefetched.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The effect of prefetching assets for common user journeys.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;optimize-images-and-fonts&quot;&gt;Optimize images and fonts &lt;a class=&quot;w-headline-link&quot; href=&quot;#optimize-images-and-fonts&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Images are a critical part of Truebil&#39;s product experience and credibility, with each product listing including up to 40 pictures. To ensure that images do not block page load, the team chose to &lt;a href=&quot;https://web.dev/image-cdns&quot;&gt;serve all their resources from a CDN&lt;/a&gt; and use &lt;a href=&quot;https://imagemagick.org/index.php&quot;&gt;imagemagick&lt;/a&gt; for image optimization. They also Gzipped all compressible resources, including images, JavaScript, and CSS, to further cut down load time.&lt;/p&gt;
&lt;p&gt;To &lt;a href=&quot;https://web.dev/avoid-invisible-text&quot;&gt;avoid a flash of invisible text&lt;/a&gt; while keeping load time as low as possible, Truebil set up their CSS to use system fonts as a fallback until external fonts have loaded.&lt;/p&gt;
&lt;h4 id=&quot;further-optimizations&quot;&gt;Further optimizations &lt;a class=&quot;w-headline-link&quot; href=&quot;#further-optimizations&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When the app was ready, the team wanted to further reduce the vendor bundle size and JavaScript execution time, so they switched their React app to Preact in production. (Learn more in the &lt;a href=&quot;https://web.dev/react&quot;&gt;React&lt;/a&gt; collection.) This approach helped them reduce the vendor bundle size from 82.3 KB to 51.2 KB.&lt;/p&gt;
&lt;h3 id=&quot;build-in-reliability&quot;&gt;Build in reliability &lt;a class=&quot;w-headline-link&quot; href=&quot;#build-in-reliability&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With a focus on the Indian market, a vast majority of Truebil&#39;s users access their product on patchy networks that sometimes fall into bandwidths as low as 2G. So building a resilient experience was critical not only to improving performance under constrained network conditions but also to delivering a product that their users could rely on—one that &lt;em&gt;always&lt;/em&gt; works.&lt;/p&gt;
&lt;h4 id=&quot;a-hybrid-caching-strategy-for-reliable-loading&quot;&gt;A hybrid caching strategy for reliable loading &lt;a class=&quot;w-headline-link&quot; href=&quot;#a-hybrid-caching-strategy-for-reliable-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The interactivity and rate of change for Truebil&#39;s content vary a lot. To ensure that &lt;em&gt;all&lt;/em&gt; its content is both fresh and reliable, the Truebil team implemented &lt;a href=&quot;https://web.dev/runtime-caching-with-workbox&quot;&gt;API caching&lt;/a&gt; using a combination of network-first, cache-first, and fastest-first strategies.&lt;/p&gt;
&lt;p&gt;For static pages, such as the subscriptions page, Truebil uses a cache-first strategy to go to their subscription API cache first, falling back to the network.&lt;/p&gt;
&lt;p&gt;For pages with dynamic content that rarely changes, such as their product listing or details pages, Truebil uses a network-first strategy so that the browser first checks the network for content before falling back to the API cache if the network is unavailable.&lt;/p&gt;
&lt;p&gt;And for dynamic pages that change often, such as the home, filter, search, and city pages, Truebil uses a fastest-first strategy to choose between network or cache based on whichever comes first. To ensure that content is fresh, the cache is updated whenever the network response differs from what&#39;s in the cache.&lt;/p&gt;
&lt;h4 id=&quot;service-workers-for-a-full-offline-experience&quot;&gt;Service workers for a full offline experience &lt;a class=&quot;w-headline-link&quot; href=&quot;#service-workers-for-a-full-offline-experience&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Even though a large part of Truebil&#39;s content is highly dynamic—cars can be added or bought at any time—the team wanted to ensure that their users had &lt;em&gt;some&lt;/em&gt; content to engage with, even if they were going through patchy networks or were completely offline.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service workers&lt;/a&gt;, the team was able to cache both static data and the dynamic data that a user has already interacted with so that the user can view it offline. To make sure users know that content might change when they come back online, the team changed the UI to grayscale to indicate offline mode. Browsing product pages is a critical part of the Truebil user journey. Users who have visited the PWA at least once can browse listings and product pages that they have visited before but won&#39;t be able to see any updates to the listing or the product.&lt;/p&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/truebil-lite/grayscale.png&quot; alt=&quot;A screenshot of the Truebil Lite app in offline mode.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Truebil Lite in offline mode.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;improve-engagement-to-keep-users-coming-back&quot;&gt;Improve engagement to keep users coming back &lt;a class=&quot;w-headline-link&quot; href=&quot;#improve-engagement-to-keep-users-coming-back&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;an-engaging-first-experience&quot;&gt;An engaging first experience &lt;a class=&quot;w-headline-link&quot; href=&quot;#an-engaging-first-experience&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Since most of their users come from paid channels, Truebil needed to supplement their fast loading web app with a product that surfaces highly relevant recommendations to increase conversions. While the team uses a recommendation system based on sophisticated filtering for existing users, their system doesn&#39;t work for users who log in for the first time.&lt;/p&gt;
&lt;p&gt;To avoid giving their first-time users a cold start, the team integrated a recommendation system using their digital marketing efforts. They add product details such as car model, price, and body type into an ad&#39;s destination URL through a UTM parameter, which is read by their recommendation system and reflected in the products surfaced. In case the sysme reads no such details in the URL, it falls back to popular cars, which is a combination of popular models, popular budgets, and cars that have been popular in the last few weeks or days.&lt;/p&gt;
&lt;h4 id=&quot;an-installable-web-app&quot;&gt;An installable web app &lt;a class=&quot;w-headline-link&quot; href=&quot;#an-installable-web-app&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Having built a fast, full-featured web app with a compelling user experience, Truebil wanted to ensure that their users would keep coming back. They realized that making the app installable would make repeat visits much more seamless.&lt;/p&gt;
&lt;p&gt;The team implemented the &lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/&quot;&gt;Add to Home Screen&lt;/a&gt; feature to make their product a full progressive web app (PWA). This approach allowed users to add Truebil Lite to the home screen and launch it in full-screen mode. And since they had already implemented an offline mode, the team was able to add the new feature easily.&lt;/p&gt;
&lt;p&gt;To ensure that their users weren&#39;t spammed and to increase the probability that users would install the app, the team recently updated their strategy for &lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/promoting-install-mobile&quot;&gt;promoting PWA installation&lt;/a&gt; so that installation prompts appear when they&#39;ll actually be useful to different kinds of users. Truebil settled on a three-part strategy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Show prompts when the user has completed an action or is idle.&lt;/li&gt;
&lt;li&gt;Show contextual prompts to mature users.&lt;/li&gt;
&lt;li&gt;Show a banner when the user has spent a set amount of time on the site.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;default-banners-on-process-completion-and-on-high-traffic-pages&quot;&gt;Default banners on process completion and on high-traffic pages &lt;a class=&quot;w-headline-link&quot; href=&quot;#default-banners-on-process-completion-and-on-high-traffic-pages&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The team decided to show an installation banner when a user completes a task or is on high-traffic pages but idle (that is, not taking an action, such as scrolling or filling out a form). This approach allowed them to avoid interrupting the user&#39;s activity.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/truebil-lite/default-banners.png&quot; alt=&quot;Screenshots of Truebil Lite&#39;s installation banner.&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;contextual-prompts-for-mature-users&quot;&gt;Contextual prompts for mature users &lt;a class=&quot;w-headline-link&quot; href=&quot;#contextual-prompts-for-mature-users&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For users who had interacted with the app for a while, the team used highly contextual custom messages to show the value of installing the app to the home screen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.dev/truebil-lite/contextual-prompts.png&quot; alt=&quot;Screenshots of Truebil Lite&#39;s contextual installation prompts for mature users.&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;a-custom-banner-for-time-based-prompts&quot;&gt;A custom banner for time-based prompts &lt;a class=&quot;w-headline-link&quot; href=&quot;#a-custom-banner-for-time-based-prompts&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Finally, the team built in a non-intrusive banner with a notification-like design that&#39;s triggered at specific events, such as opening a listing page or after the user has spent a set amount of time spent in the app:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/truebil-lite/notification.png&quot; alt=&quot;A screenshot of Truebil Lite&#39;s time-based installation prompt banner.&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;Because of these improvements, Truebil&#39;s conversion and engagement rates have grown significantly with &lt;strong&gt;26% longer user sessions&lt;/strong&gt; and &lt;strong&gt;61% more conversions&lt;/strong&gt;, which is significant for their business given the high transaction value of each conversion.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a startup with limited resources, choosing the right platform can be critical to the success of the business. Moving to a PWA focused on speed, resilience, and engagement, enabled us to increase our revenue-to-marketing spend by &lt;strong&gt;80%&lt;/strong&gt; thanks to increased conversions and the frictionless reach of the web.&lt;/p&gt;
&lt;cite&gt;Rakesh Raman, Co-Founder and Chief of Product &amp; Data Science at Truebil&lt;/cite&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;w-stats&quot;&gt;
&lt;div class=&quot;w-stat&quot;&gt;
&lt;p class=&quot;w-stat__figure&quot;&gt;44&lt;sub class=&quot;w-stat__sub&quot;&gt;%&lt;/sub&gt;&lt;/p&gt;
&lt;p class=&quot;w-stat__desc&quot;&gt;Improvement in loading time&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;w-stat&quot;&gt;
&lt;p class=&quot;w-stat__figure&quot;&gt;26&lt;sub class=&quot;w-stat__sub&quot;&gt;%&lt;/sub&gt;&lt;/p&gt;
&lt;p class=&quot;w-stat__desc&quot;&gt;Longer user sessions&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;w-stat&quot;&gt;
&lt;p class=&quot;w-stat__figure&quot;&gt;61&lt;sub class=&quot;w-stat__sub&quot;&gt;%&lt;/sub&gt;&lt;/p&gt;
&lt;p class=&quot;w-stat__desc&quot;&gt;Increase in conversions&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;w-stat&quot;&gt;
&lt;p class=&quot;w-stat__figure&quot;&gt;80&lt;sub class=&quot;w-stat__sub&quot;&gt;%&lt;/sub&gt;&lt;/p&gt;
&lt;p class=&quot;w-stat__desc&quot;&gt;Increase in revenue-to-marketing spend&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content>
<author>
<name>Harleen Batra</name>
</author>
</entry>
<entry>
<title>How to install the Thumbor image CDN</title>
<link href="https://web.dev/install-thumbor/"/>
<updated>2019-08-13T17:00:00-07:00</updated>
<id>https://web.dev/install-thumbor/</id>
<content type="html">&lt;p&gt;Image CDNs make it easy to dynamically optimize the aesthetics and performance of your images. Unlike most image CDNs, &lt;a href=&quot;http://thumbor.org/&quot;&gt;Thumbor&lt;/a&gt; is open-source and can be used for free to resize, compress, and transform images. It&#39;s suitable for production use; &lt;a href=&quot;https://wikitech.wikimedia.org/wiki/Thumbor&quot;&gt;Wikipedia&lt;/a&gt; and &lt;a href=&quot;https://medium.com/square-corner-blog/dynamic-images-with-thumbor-a430a1cfcd87&quot;&gt;Square&lt;/a&gt; both use Thumbor.&lt;/p&gt;
&lt;p&gt;This guide explains how to install Thumbor on your own server. Once installed, you&#39;ll be able to use Thumbor as an API for transforming your images.&lt;/p&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro &lt;a class=&quot;w-headline-link&quot; href=&quot;#intro&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You&#39;ll be installing Thumbor on a VM running Ubuntu 16.04. Ubuntu 16.04 is a very common image and these instructions are intended to work on any cloud provider. Creating a VM might sound like more work than installing Thumbor on your local machine, but the minutes that you take to create a VM will probably save you hours or days of frustration trying to get Thumbor to properly install on your local machine. Although easy to use, Thumbor is notoriously difficult to install but these instructions simplify the process. If dependencies download quickly, the installation can be completed in 5 to 10 minutes.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites &lt;a class=&quot;w-headline-link&quot; href=&quot;#prerequisites&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This post assumes that you know how to create a Ubuntu 16.04 LTS VM on a cloud platform like &lt;a href=&quot;https://cloud.google.com/compute/docs/instances/create-start-instance&quot;&gt;Google Cloud&lt;/a&gt;, &lt;a href=&quot;https://aws.amazon.com/getting-started/tutorials/launch-a-virtual-machine/&quot;&gt;AWS,&lt;/a&gt; or &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?toc=%2Fazure%2Fvirtual-machines%2Flinux%2Ftoc.json&quot;&gt;Azure&lt;/a&gt; and how to use command line tools to set up the VM.&lt;/p&gt;
&lt;h2 id=&quot;install-thumbor-dependencies&quot;&gt;Install Thumbor Dependencies &lt;a class=&quot;w-headline-link&quot; href=&quot;#install-thumbor-dependencies&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Update and upgrade Ubuntu&#39;s already-installed packages:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; update -y &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; upgrade -y&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Install &lt;code&gt;pip&lt;/code&gt;, the package manager for Python. Later you&#39;ll install Thumbor with &lt;code&gt;pip&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; -y python-pip&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Install Thumbor&#39;s dependencies. Thumbor&#39;s documentation does not explicitly mention these dependencies, but Thumbor will not install successfully without them.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ssl packages&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; -y libcurl4-openssl-dev libssl-dev&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# computer vision packages&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; -y python-opencv libopencv-dev&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# image format packages&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; -y libjpeg-dev libpng-dev libwebp-dev webp&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;install-thumbor&quot;&gt;Install Thumbor &lt;a class=&quot;w-headline-link&quot; href=&quot;#install-thumbor&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Install Thumbor using pip.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; pip &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; thumbor&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Note: Many Python developers use &lt;a href=&quot;https://pypi.org/project/virtualenv/&quot;&gt;virtualenv&lt;/a&gt; to manage their packages. For the sake of simplicity, these instructions do not use &lt;code&gt;virtualenv&lt;/code&gt;. If you are installing Thumbor in a standalone environment, &lt;code&gt;virtualenv&lt;/code&gt; is not necessary. If you choose to use &lt;code&gt;virtualenv&lt;/code&gt;, note that Thumbor requires Python 2.7 and will not work with newer versions of &lt;code&gt;pip&lt;/code&gt; (e.g., these instructions use &lt;code&gt;pip&lt;/code&gt; 8.1.1).&lt;/p&gt;
&lt;p&gt;If you&#39;ve successfully installed Thumbor, this should work:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;thumbor --help&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;run-thumbor&quot;&gt;Run Thumbor &lt;a class=&quot;w-headline-link&quot; href=&quot;#run-thumbor&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Run Thumbor. Debug logging is optional but can be helpful when you&#39;re getting started.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;thumbor --log-level debug&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Thumbor is now running.&lt;/p&gt;
&lt;h2 id=&quot;open-firewall-port&quot;&gt;Open Firewall Port &lt;a class=&quot;w-headline-link&quot; href=&quot;#open-firewall-port&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, Thumbor runs on port 8888. If your VM&#39;s IP address is &lt;code&gt;12.123.12.122&lt;/code&gt;, then you would access Thumbor from the web browser at &lt;code&gt;http://12.123.12.123:8888/.../$IMAGE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, this probably won&#39;t work for you (yet) because cloud providers usually require that you explicitly open firewall ports before they will accept incoming traffic.&lt;/p&gt;
&lt;p&gt;Update the firewall to expose port 8888. Here&#39;s more information on how to do this for: &lt;a href=&quot;https://cloud.google.com/vpc/docs/using-firewalls&quot;&gt;Google Cloud&lt;/a&gt;, &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/authorizing-access-to-an-instance.html&quot;&gt;AWS&lt;/a&gt;, and &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/virtual-machines/windows/nsg-quickstart-portal&quot;&gt;Azure&lt;/a&gt;. Note that for Google Cloud you need to first &lt;a href=&quot;https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address&quot;&gt;assign a static IP address to your VM&lt;/a&gt; and then &lt;a href=&quot;https://cloud.google.com/vpc/docs/special-configurations#externalhttpconnection&quot;&gt;allow an external HTTP connection&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;try-it-out&quot;&gt;Try It Out &lt;a class=&quot;w-headline-link&quot; href=&quot;#try-it-out&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thumbor is now accessible and ready for use. Try it out by visiting the following URL:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;http://YOUR_VIRTUAL_MACHINE:8888/unsafe/100x100/https://web.dev/install-thumbor/hero.jpg&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Note that this URL uses HTTP. Thumbor uses HTTP by default but can be &lt;a href=&quot;https://thumbor.readthedocs.io/en/latest/image_loader.html&quot;&gt;configured&lt;/a&gt; to use HTTPS.&lt;/p&gt;
&lt;p&gt;You should see an image that is 100 pixels wide by 100 pixels tall. Thumbor has taken the image &lt;code&gt;hero.jpg&lt;/code&gt; and size specified in the URL string and served the result. You can replace the image in the URL string (i.e., &lt;code&gt;https://web.dev/install-thumbor/hero.jpg&lt;/code&gt;) with any other image (e.g., &lt;code&gt;https://your-site.com/cat.jpg&lt;/code&gt;) and Thumbor will resize that image too.&lt;/p&gt;
&lt;h2 id=&quot;appendix:-configuring-systemd&quot;&gt;Appendix: Configuring Systemd &lt;a class=&quot;w-headline-link&quot; href=&quot;#appendix:-configuring-systemd&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This step explains how to make sure that the Thumbor process keeps running, even after the VM has been restarted. This step is important for production sites, but optional if you&#39;re just playing around with Thumbor.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/systemd.html&quot;&gt;Systemd&lt;/a&gt; is the &amp;quot;system and service manager&amp;quot; for Linux operating systems. &lt;code&gt;systemd&lt;/code&gt; makes it easy to configure when services (processes) run.&lt;/p&gt;
&lt;p&gt;You will be configuring &lt;code&gt;systemd&lt;/code&gt; to automatically start Thumbor on VM boot. If the VM is restarted, the Thumbor process will automatically restart as well. This is much more reliable than relying on user intervention to start Thumbor.&lt;/p&gt;
&lt;p&gt;Navigate to the &lt;code&gt;/lib/systemd/system&lt;/code&gt; directory. This directory contains the service files for &lt;code&gt;systemd&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cd&lt;/span&gt; /lib/systemd/system&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;As superuser, create a &lt;code&gt;thumbor.service&lt;/code&gt; file.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; thumbor.service&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Using your favorite text editor (vim and nano come pre-installed on Ubuntu or you can install another editor), add the following configuration to &lt;code&gt;thumbor.service&lt;/code&gt;. This configuration will run &lt;code&gt;/usr/local/bin/thumbor&lt;/code&gt; (i.e. the Thumbor binary) once networking is available and will restart Thumbor on &lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/systemd.service.html#Restart=&quot;&gt;failure&lt;/a&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;[Unit]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;Description=Service for Thumbor image CDN&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;Documentation=https://thumbor.readthedocs.io/en/latest/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;After=network.target&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;[Service]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;ExecStart=/usr/local/bin/thumbor&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;Restart=on-failure&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;[Install]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;WantedBy=multi-user.target&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;&lt;code&gt;systemctl&lt;/code&gt; is the utility used to manage &lt;code&gt;systemd&lt;/code&gt;. Use the &lt;code&gt;start&lt;/code&gt; command to start Thumbor.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl start thumbor.service&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Note: If Thumbor is currently running, you should stop it before attempting to start Thumbor using &lt;code&gt;systemctl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, &amp;quot;enable&amp;quot; Thumbor. This means that Thumbor will automatically start on boot.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token function&quot;&gt;enable&lt;/span&gt; thumbor.service&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Verify that you&#39;ve successfully configured &lt;code&gt;systemd&lt;/code&gt; by running the &lt;code&gt;status&lt;/code&gt; command.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;systemctl status thumbor.service&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;If you&#39;ve successfully set up thumbor.service to use &lt;code&gt;systemd&lt;/code&gt;, the &lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/systemctl.html#status%20PATTERN%E2%80%A6%7CPID%E2%80%A6%5D&quot;&gt;status&lt;/a&gt; should show that it is enabled and active.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--center&quot;&gt;
&lt;img src=&quot;https://web.dev/install-thumbor/systemd.jpg&quot; alt=&quot;Systemctl displaying the status of Thumbor&quot; class=&quot;w-screenshot&quot;&gt;
&lt;/figure&gt;
</content>
<author>
<name>Katie Hempenius</name>
</author>
</entry>
<entry>
<title>Third-party JavaScript performance</title>
<link href="https://web.dev/third-party-javascript/"/>
<updated>2019-08-12T17:00:00-07:00</updated>
<id>https://web.dev/third-party-javascript/</id>
<content type="html">&lt;p&gt;Third-party JavaScript generally refers to scripts embedded in your website that are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not authored by you&lt;/li&gt;
&lt;li&gt;Served from third-party servers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sites use these scripts for various purposes, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Social sharing buttons&lt;/li&gt;
&lt;li&gt;Video player embeds&lt;/li&gt;
&lt;li&gt;Chat services&lt;/li&gt;
&lt;li&gt;Advertising iframes&lt;/li&gt;
&lt;li&gt;Analytics and metrics scripts&lt;/li&gt;
&lt;li&gt;A/B testing scripts for experiments&lt;/li&gt;
&lt;li&gt;Helper libraries (like date formatting, animation, and functional libraries)&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;w-figure w-figure--fullbleed&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
&lt;source src=&quot;https://web.dev/third-party-javascript/third-party-examples.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Third-party scripts can provide powerful functionality, but that&#39;s not the whole story. They also affect privacy, security, and page behavior⁠—and they can be particularly problematic for performance.&lt;/p&gt;
&lt;h2 id=&quot;performance&quot;&gt;Performance &lt;a class=&quot;w-headline-link&quot; href=&quot;#performance&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Any significant amount of &lt;a href=&quot;https://web.dev/bootup-time&quot;&gt;JavaScript can slow down performance&lt;/a&gt;. But because third-party JavaScript is usually outside your control, it can bring additional issues.&lt;/p&gt;
&lt;h3 id=&quot;network&quot;&gt;Network &lt;a class=&quot;w-headline-link&quot; href=&quot;#network&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Setting up connections takes time, and sending too many requests to multiple servers causes slowdowns. That time is even longer for secure connections, which may involve DNS lookups, redirects, and several round trips to the final server that handles the user&#39;s request.&lt;/p&gt;
&lt;p&gt;Third-party scripts often add to network overhead with things such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firing additional network requests&lt;/li&gt;
&lt;li&gt;Pulling in unoptimized images and videos&lt;/li&gt;
&lt;li&gt;Insufficient &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching&quot;&gt;HTTP caching&lt;/a&gt;, which forces frequent fetching of network resources&lt;/li&gt;
&lt;li&gt;Insufficient &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer&quot;&gt;server compression&lt;/a&gt; of resources&lt;/li&gt;
&lt;li&gt;Multiple instances of frameworks and libraries pulled in by different third-party embeds&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;rendering&quot;&gt;Rendering &lt;a class=&quot;w-headline-link&quot; href=&quot;#rendering&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The way third-party JavaScript is loaded matters a lot. If it&#39;s done synchronously in the critical rendering path it delays parsing of the rest of the document.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--key-term&quot;&gt;
&lt;p&gt;&lt;strong&gt;Key Term:&lt;/strong&gt;
The &lt;strong&gt;critical rendering path&lt;/strong&gt; includes all resources that the browser needs to display the first screen&#39;s worth of content.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;If a third party has server issues and fails to deliver a resource, rendering is blocked until the request times out, which can be anywhere from 10 to 80 seconds. You can test and simulate this problem with &lt;a href=&quot;https://css-tricks.com/use-webpagetest-api/#single-point-of-failure&quot;&gt;WebPageTest Single-Point-of-Failure tests&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/#ab_test_smaller_samples_of_users&quot;&gt;A/B testing scripts&lt;/a&gt; can also often delay rendering. Most of them block content display until they complete processing—which can be true even for asynchronous A/B testing scripts.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;what-to-do-about-it&quot;&gt;What to do about it &lt;a class=&quot;w-headline-link&quot; href=&quot;#what-to-do-about-it&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using third-party JavaScript is often unavoidable, but there are things you can do to minimize adverse effects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When choosing third-party resources, favor those that send the least amount of code while still giving you the functionality you need.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://web.dev/use-lighthouse-for-performance-budgets/&quot;&gt;performance budgets&lt;/a&gt; for third-party content to keep their cost in check.&lt;/li&gt;
&lt;li&gt;Don&#39;t use the same functionality from two different vendors. You probably don&#39;t need two tag managers or two analytics platforms.&lt;/li&gt;
&lt;li&gt;Routinely audit and clean out redundant third-party scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To learn how to audit third-party content and load it efficiently for better performance and user experience, check out the other posts in the &lt;a href=&quot;https://web.dev/fast/#optimize-your-third-party-resources&quot;&gt;Optimize your third-party resources&lt;/a&gt; section.&lt;/p&gt;
</content>
<author>
<name>Milica Mihajlija</name>
</author>
</entry>
<entry>
<title>More capable form controls</title>
<link href="https://web.dev/more-capable-form-controls/"/>
<updated>2019-08-07T17:00:00-07:00</updated>
<id>https://web.dev/more-capable-form-controls/</id>
<content type="html">&lt;p&gt;Many developers build custom form controls, either to provide controls that aren&#39;t built in to the browser, or to customize the look and feel beyond what&#39;s possible with the native form controls.&lt;/p&gt;
&lt;p&gt;However, it can be difficult to replicate the features of built-in HTML form controls. Consider some of the features an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element gets automatically when you add it to a form:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The input is automatically added to the form&#39;s list of controls.&lt;/li&gt;
&lt;li&gt;The input&#39;s value is automatically submitted with the form.&lt;/li&gt;
&lt;li&gt;The input participates in &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation&quot;&gt;form validation&lt;/a&gt;. You can style the input using the &lt;code&gt;:valid&lt;/code&gt; and &lt;code&gt;:invalid&lt;/code&gt; pseudoclasses.&lt;/li&gt;
&lt;li&gt;The input is notified when the form is reset, when the form is reloaded, or when the browser tries to autofill form entries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Custom form controls typically have few of these features. Developers can work around some of the limitations in JavaScript, like adding a hidden &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; to a form to participate in form submission. But other features just can&#39;t be replicated in JavaScript alone.&lt;/p&gt;
&lt;p&gt;Two new web features make it easier to build custom form controls, and remove the limitations of current custom controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;formdata&lt;/code&gt; event lets an arbitrary JavaScript object participate in form submission, so you can add form data without using a hidden &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Form-associated custom elements API lets custom elements act more like built-in form controls.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two features can be used to create new kinds of controls that work better with native web forms.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Building custom form controls is an advanced topic. This article assumes a certain knowledge of forms and form controls. When building a custom form control, there are many factors to consider, especially making sure that your controls are accessible to all users. To learn more about forms, go to the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms&quot;&gt;MDN guide on forms&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;event-based-api&quot;&gt;Event-based API &lt;a class=&quot;w-headline-link&quot; href=&quot;#event-based-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;formdata&lt;/code&gt; event is a low-level API that lets any JavaScript code participate in form submission. The mechanism works like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You add a &lt;code&gt;formdata&lt;/code&gt; event listener to the form you want to interact with.&lt;/li&gt;
&lt;li&gt;When a user clicks the submit button, the form fires a &lt;code&gt;formdata&lt;/code&gt; event, which includes a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData&quot;&gt;&lt;code&gt;FormData&lt;/code&gt;&lt;/a&gt; object that holds all of the data being submitted.&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;formdata&lt;/code&gt; listener gets a chance to add to or modify the data before the form is submitted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&#39;s an example of sending a single value in a &lt;code&gt;formdata&lt;/code&gt; event listener:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; form &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;form&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// FormData event is sent on &amp;lt;form&gt; submission, before transmission.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// The event has a formData property&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;formdata&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;formData&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// https://developer.mozilla.org/en-US/docs/Web/API/FormData&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; formData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; myInputValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Try this out using our example on Glitch. Be sure to run it on Chrome 77 or later to see the API in action.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
&lt;iframe allow=&quot;geolocation; microphone; camera; midi; encrypted-media&quot; src=&quot;https://glitch.com/embed/#!/embed/formdata-event?path=index.html&amp;amp;previewSize=0&quot; alt=&quot;formdata event demo on Glitch&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;form-associated-custom-elements&quot;&gt;Form-associated custom elements &lt;a class=&quot;w-headline-link&quot; href=&quot;#form-associated-custom-elements&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use the event-based API with any kind of component, but it only allows you to interact with the submission process.&lt;/p&gt;
&lt;p&gt;Native form controls participate in many parts of the form lifecycle besides submission. Form-associated custom elements aim to bridge the gap between custom widgets and native controls. Form-associated custom elements match many of the features of native form elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you place a form-associated custom element inside a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;, it&#39;s automatically associated with the form, like a native control.&lt;/li&gt;
&lt;li&gt;The element can be labeled using a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;The element can set a value that&#39;s automatically submitted with the form.&lt;/li&gt;
&lt;li&gt;The element can set a flag indicating whether or not it has valid input. If one of the form controls has invalid input, the form can&#39;t be submitted.&lt;/li&gt;
&lt;li&gt;The element can provide callbacks for various parts of the form lifecycle—such as when the form is disabled or reset to its default state.&lt;/li&gt;
&lt;li&gt;The element supports the standard CSS pseudoclasses for form controls, like &lt;code&gt;:disabled&lt;/code&gt; and &lt;code&gt;:invalid&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&#39;s a lot of features! This article won&#39;t touch on all of them, but will describe the basics needed to integrate your custom element with a form.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;This section assumes a basic familiarity with custom elements. For an introduction to custom elements, see &lt;a href=&quot;https://developers.google.com/web/fundamentals/web-components/customelements&quot;&gt;Custom Elements v1: Reusable Web Components&lt;/a&gt; on Web Fundamentals.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;defining-a-form-associated-custom-element&quot;&gt;Defining a form-associated custom element &lt;a class=&quot;w-headline-link&quot; href=&quot;#defining-a-form-associated-custom-element&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To turn a custom element into a form-associated custom element requires a few extra steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a static &lt;code&gt;formAssociated&lt;/code&gt; property to your custom element class. This tells the browser to treat the element like a form control.&lt;/li&gt;
&lt;li&gt;Call the &lt;code&gt;attachInternals()&lt;/code&gt; method on the element to get access to extra methods and properties for form controls, like &lt;code&gt;setFormValue()&lt;/code&gt; and &lt;code&gt;setValidity()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the common properties and methods supported by form controls, like &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, and &lt;code&gt;validity&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s how those items fit into a basic custom element definition:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Form-associated custom elements must be autonomous custom elements--&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// meaning they must extend HTMLElement, not one of its subclasses.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyCounter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Identify the element as a form-associated custom element&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; formAssociated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Get access to the internal form control APIs&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;attachInternals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// internal value for this control&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Form controls usually expose a &quot;value&quot; property&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; v&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// The following properties and methods aren&#39;t strictly required, &lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// but native form controls provide them. Providing them helps&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// ensure consistency with native controls.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;name&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;localName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;validity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;validity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;validationMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;validationMessage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;willValidate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;willValidate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;checkValidity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;checkValidity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;reportValidity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reportValidity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; … &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;customElements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-counter&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; MyCounter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Once registered, you can use this element wherever you&#39;d use a native form control:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Number of bunnies: &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;my-counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;my-counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Submit&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;setting-a-value&quot;&gt;Setting a value &lt;a class=&quot;w-headline-link&quot; href=&quot;#setting-a-value&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;attachInternals()&lt;/code&gt; method returns an &lt;code&gt;ElementInternals&lt;/code&gt; object that provides access to form control APIs. The most basic of these is the &lt;code&gt;setFormValue()&lt;/code&gt; method, which sets the current value of the control.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;setFormValue()&lt;/code&gt; method can take one of three types of values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A string value.&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/File&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object.&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData&quot;&gt;&lt;code&gt;FormData&lt;/code&gt;&lt;/a&gt; object. You can use a &lt;code&gt;FormData&lt;/code&gt; object to pass multiple values (for example, a credit card input control might pass a card number, expiration date, and verification code).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To set a simple value:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFormValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;To set multiple values, you can do something like this:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Use the control&#39;s name as the base name for submitted data&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;name&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;entries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;-first-name&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstName_&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;entries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;-last-name&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastName_&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFormValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;The &lt;code&gt;setFormValue()&lt;/code&gt; method takes a second, optional &lt;code&gt;state&lt;/code&gt; parameter, used to store the internal state of the control. For more information, see &lt;a href=&quot;#restoring-form-state&quot;&gt;Restoring form state&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;input-validation&quot;&gt;Input validation &lt;a class=&quot;w-headline-link&quot; href=&quot;#input-validation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Your control can also participate in form validation by calling the &lt;code&gt;setValidity()&lt;/code&gt;
method on the internals object.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Assume this is called whenever the internal value is updated&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;onUpdateValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;:disabled&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;required&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_ &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setValidity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;customError&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Value cannot be negative.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setValidity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFormValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;You can style a form-associated custom element with the &lt;code&gt;:valid&lt;/code&gt; and &lt;code&gt;:invalid&lt;/code&gt;
pseudoclasses, just like a built-in form control.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Although you can set a validation message, Chrome currently
fails to display the validation message for form-associated custom elements.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;lifecycle-callbacks&quot;&gt;Lifecycle callbacks &lt;a class=&quot;w-headline-link&quot; href=&quot;#lifecycle-callbacks&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A form-associated custom element API includes a set of extra lifecycle callbacks to tie in to the form lifecycle. The callbacks are optional: only implement a callback if your element needs to do something at that point in the lifecycle.&lt;/p&gt;
&lt;h4 id=&quot;void-formassociatedcallback(form)&quot;&gt;&lt;code&gt;void formAssociatedCallback(form)&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#void-formassociatedcallback(form)&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Called when the browser associates the element with a form element, or disassociates the element from a form element.&lt;/p&gt;
&lt;h4 id=&quot;void-formdisabledcallback(disabled)&quot;&gt;&lt;code&gt;void formDisabledCallback(disabled)&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#void-formdisabledcallback(disabled)&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Called after the &lt;code&gt;disabled&lt;/code&gt; state of the element changes, either because the &lt;code&gt;disabled&lt;/code&gt; attribute of this element was added or removed; or because the &lt;code&gt;disabled&lt;/code&gt; state changed on a &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; that&#39;s an ancestor of this element. The &lt;code&gt;disabled&lt;/code&gt; parameter represents the new &lt;a href=&quot;https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled&quot;&gt;disabled state&lt;/a&gt; of the element. The element may, for example, disable elements in its shadow DOM when it is disabled.&lt;/p&gt;
&lt;h4 id=&quot;void-formresetcallback()&quot;&gt;&lt;code&gt;void formResetCallback()&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#void-formresetcallback()&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Called after the form is reset. The element should reset itself to some kind of default state. For native inputs, this usually involves setting the &lt;code&gt;value&lt;/code&gt; property to match the &lt;code&gt;value&lt;/code&gt; attribute set in markup (or in the case of a checkbox, setting the &lt;code&gt;checked&lt;/code&gt; property to match the &lt;code&gt;checked&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h4 id=&quot;void-formstaterestorecallback(state-mode)&quot;&gt;&lt;code&gt;void formStateRestoreCallback(state, mode)&lt;/code&gt; &lt;a class=&quot;w-headline-link&quot; href=&quot;#void-formstaterestorecallback(state-mode)&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Called in one of two circumstances:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the browser restores the state of the element (for example, after a navigation, or when the browser restarts). The &lt;code&gt;mode&lt;/code&gt; argument is &lt;code&gt;&amp;quot;restore&amp;quot;&lt;/code&gt; in this case.&lt;/li&gt;
&lt;li&gt;When the browser&#39;s input-assist features such as form autofilling sets a value. The &lt;code&gt;mode&lt;/code&gt; argument is &lt;code&gt;&amp;quot;autocomplete&amp;quot;&lt;/code&gt; in this case.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The type of the first argument depends on how the &lt;code&gt;setFormValue()&lt;/code&gt; method was called. For more details, see &lt;a href=&quot;#restoring-form-state&quot;&gt;Restoring form state&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;restoring-form-state&quot;&gt;Restoring form state &lt;a class=&quot;w-headline-link&quot; href=&quot;#restoring-form-state&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Under some circumstances—like when navigating back to a page, or restarting the browser, the browser may try to restore the form to the state the user left it in.&lt;/p&gt;
&lt;p&gt;For a form-associated custom element, the restored state comes from the value(s) you pass to the &lt;code&gt;setFormValue()&lt;/code&gt; method. You can call the method with a single value parameter, as shown in the &lt;a href=&quot;#set-a-value&quot;&gt;earlier examples&lt;/a&gt;, or with two parameters:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFormValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The &lt;code&gt;value&lt;/code&gt; represents the submittable value of the control. The optional &lt;code&gt;state&lt;/code&gt; parameter is an &lt;em&gt;internal&lt;/em&gt; representation of the state of the control, which can include data that doesn&#39;t get sent to the server. The &lt;code&gt;state&lt;/code&gt; parameter takes the same types as the &lt;code&gt;value&lt;/code&gt; parameter—it can be a string, &lt;code&gt;File&lt;/code&gt;, or &lt;code&gt;FormData&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;state&lt;/code&gt; parameter is useful when you can&#39;t restore a control&#39;s state based on the value alone. For example, suppose you create a color picker with multiple modes: a palette or an RGB color wheel. The submittable &lt;em&gt;value&lt;/em&gt; would be the selected color in a canonical form, like &lt;code&gt;&amp;quot;#7fff00&amp;quot;&lt;/code&gt;. But to restore the control to a specific state, you&#39;d also need to know which mode it was in, so the &lt;em&gt;state&lt;/em&gt; might look like &lt;code&gt;&amp;quot;palette/#7fff00&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internals_&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFormValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mode_ &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Your code would need to restore its state based on the stored state value.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;formStateRestoreCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mode &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;restore&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// expects a state parameter in the form &#39;controlMode/value&#39;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;controlMode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mode_ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; controlMode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Chrome currently doesn&#39;t handle autofill for form-associated &lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// custom elements. In the autofill case, you might need to handle&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// a raw value.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;In the case of a simpler control (for example a number input), the value is probably sufficient to restore the control to its previous state. If you omit &lt;code&gt;state&lt;/code&gt; when calling &lt;code&gt;setFormValue()&lt;/code&gt;, then the value is passed to &lt;code&gt;formStateRestoreCallback()&lt;/code&gt;.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;formStateRestoreCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Simple case, restore the saved value&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value_ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h3 id=&quot;a-working-example&quot;&gt;A working example &lt;a class=&quot;w-headline-link&quot; href=&quot;#a-working-example&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following example puts together many of the features of form-associated custom elements.
Be sure to run it on Chrome 77 or later to see the API in action.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
&lt;iframe allow=&quot;geolocation; microphone; camera; midi; encrypted-media&quot; src=&quot;https://glitch.com/embed/#!/embed/form-associated-ce?path=public%2Fmy-control.js&amp;amp;previewSize=0&quot; alt=&quot;formdata event demo on Glitch&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;w-headline-link&quot; href=&quot;#feature-detection&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use feature detection to determine whether the &lt;code&gt;formdata&lt;/code&gt; event and form-associated custom elements are available. There are currently no polyfills released for either feature. In both cases, you can fall back to adding a hidden form element to propagate the control&#39;s value to the form. Many of the more advanced features of form-associated custom elements will likely be difficult or impossible to polyfill.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FormDataEvent&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// formdata event is supported&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ElementInternals&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token string&quot;&gt;&#39;setFormData&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ElementInternals&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Form-associated custom elements are supported&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;w-headline-link&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;formdata&lt;/code&gt; event and form-associated custom elements provide new tools for creating custom form controls.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;formdata&lt;/code&gt; event doesn&#39;t give you any new capabilities, but it gives you an interface for adding your form data to the submit process, without having to create a hidden &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;The form-associated custom elements API provides a new set of capabilities for making custom form controls that work like built-in form controls.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hero image by Oudom Pravat on &lt;a href=&quot;https://unsplash.com/photos/yQi4mAFmRew&quot;&gt;Unsplash&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
<author>
<name>Arthur Evans</name>
</author>
</entry>
<entry>
<title>A contact picker for the web</title>
<link href="https://web.dev/contact-picker/"/>
<updated>2019-08-06T17:00:00-07:00</updated>
<id>https://web.dev/contact-picker/</id>
<content type="html">&lt;h2 id=&quot;what&quot;&gt;What is the Contact Picker API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#what&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;style&gt;
#video-demo { max-height: 600px; }
&lt;/style&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;a href=&quot;https://storage.googleapis.com/webfundamentals-assets/contact-picker/contact-picker.mp4&quot;&gt;
&lt;video id=&quot;video-demo&quot; loop=&quot;&quot; autoplay=&quot;&quot; muted=&quot;&quot; poster=&quot;https://web.dev/contact-picker/contact-picker-demo.jpg&quot; class=&quot;w-screenshot&quot;&gt;
&lt;source type=&quot;video/webm&quot; src=&quot;https://storage.googleapis.com/webfundamentals-assets/contact-picker/contact-picker.webm&quot;&gt;
&lt;source type=&quot;video/mp4&quot; src=&quot;https://storage.googleapis.com/webfundamentals-assets/contact-picker/contact-picker.mp4&quot;&gt;
&lt;/video&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;Access to the user&#39;s contacts has been a feature of native apps since
(almost) the dawn of time. It&#39;s one of the most common feature requests
I hear from web developers, and is often the key reason they build a native
app.&lt;/p&gt;
&lt;p&gt;Available by default in Chrome 80, the &lt;a href=&quot;https://wicg.github.io/contact-api/spec/&quot;&gt;Contact Picker API&lt;/a&gt; is an
on-demand API that allows users to select entries from their contact list and
share limited details of the selected entries with a website. It allows users to
share only what they want, when they want, and makes it easier for users to
reach and connect with their friends and family.&lt;/p&gt;
&lt;p&gt;For example, a web-based email client could use the Contact Picker API to
select the recipient(s) of an email. A voice-over-IP app could look up
which phone number to call. Or a social network could help a user discover
which friends have already joined.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--caution&quot;&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;
The Chrome team has put a lot of thought into the design and implementation
of the Contact Picker API to ensure that the browser will only share exactly
what people choose. See the &lt;a href=&quot;#security-considerations&quot;&gt;Security and Privacy&lt;/a&gt;
section below.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;status&quot;&gt;Current status &lt;a class=&quot;w-headline-link&quot; href=&quot;#status&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;div class=&quot;w-table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Create explainer&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/WICG/contact-api/&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Create initial draft of specification&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://wicg.github.io/contact-api/spec/&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Gather feedback &amp;amp; iterate on design&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://wicg.github.io/contact-api/spec/&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Origin trial&lt;/td&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. Launch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Chrome 80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;how-to-use&quot;&gt;Using the Contact Picker API &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-to-use&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Contact Picker API requires a single API call with an options parameter
that specifies the types of contact information you want.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://contact-picker.glitch.me/&quot;&gt;Contact Picker API demo&lt;/a&gt;
and view the
&lt;a href=&quot;https://glitch.com/edit/#!/contact-picker?path=demo.js:20:0&quot;&gt;source&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;w-headline-link&quot; href=&quot;#feature-detection&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To check if the Contact Picker API is supported, use:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supported &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;contacts&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ContactsManager&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;In addition, on Android, the Contact Picker requires Android M or later.&lt;/p&gt;
&lt;h3 id=&quot;opening-the-contact-picker&quot;&gt;Opening the Contact Picker &lt;a class=&quot;w-headline-link&quot; href=&quot;#opening-the-contact-picker&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The entry point to the Contact Picker API is &lt;code&gt;navigator.contacts.select()&lt;/code&gt;.
When called, it returns a promise and shows the contact picker, allowing the
user to select the contact(s) they want to share with the site. After
selecting what to share and clicking &lt;strong&gt;Done&lt;/strong&gt;, the promise resolves with an
array of contacts selected by the user.&lt;/p&gt;
&lt;p&gt;When calling &lt;code&gt;select()&lt;/code&gt; you must provide an array of properties you&#39;d like
returned as the first parameter, and optionally whether multiple contacts can be
selected as a second parameter.&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; props &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;name&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;email&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; opts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;multiple&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contacts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token function&quot;&gt;handleResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contacts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Handle any errors here.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;The Contacts Picker API can only be called from a &lt;a href=&quot;https://w3c.github.io/webappsec-secure-contexts/&quot;&gt;secure&lt;/a&gt;,
top-level browsing context, and like other powerful APIs, it requires a
user gesture.&lt;/p&gt;
&lt;h3 id=&quot;handling-the-results&quot;&gt;Handling the results &lt;a class=&quot;w-headline-link&quot; href=&quot;#handling-the-results&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Contact Picker API returns an array of contacts, and each contact includes
an array of the requested properties. If a contact doesn&#39;t have data for the
requested property, or the user chooses to opt-out of sharing a particular
property, the API returns an empty array. (I describe how the user chooses properties
in the &lt;a href=&quot;#security-control&quot;&gt;User control&lt;/a&gt; section.)&lt;/p&gt;
&lt;p&gt;For example, if a site requests &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;tel&lt;/code&gt;, and a user
selects a single contact that has data in the name field, provides two
phone numbers, but does not have an email address, the response returned will be:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Queen O&#39;Hearts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt; &lt;span class=&quot;token property&quot;&gt;&quot;tel&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;+1-206-555-1000&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;+1-206-555-1111&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;div class=&quot;w-aside w-aside--caution&quot;&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;
Labels and other semantic information on contact fields are dropped.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;security-considerations&quot;&gt;Security and permissions &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-considerations&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chrome team designed and implemented the Contact Picker API using the core
principles defined in
&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/lkgr/docs/security/permissions-for-powerful-web-platform-features.md&quot;&gt;Controlling Access to Powerful Web Platform Features&lt;/a&gt;,
including user control, transparency, and ergonomics. I&#39;ll explain each.&lt;/p&gt;
&lt;h3 id=&quot;security-control&quot;&gt;User control &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-control&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Access to the users&#39; contacts is via the picker, and it can only be called with
a user gesture, on a &lt;a href=&quot;https://w3c.github.io/webappsec-secure-contexts/&quot;&gt;secure&lt;/a&gt;, top-level browsing context.
This ensures that a site can&#39;t show the picker on page load, or randomly show
the picker without any context.&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;img src=&quot;https://web.dev/contact-picker/contact-picker-user-choice.jpg&quot; class=&quot;w-screenshot&quot; alt=&quot;Screen shot, users can choose which properties to share.&quot; width=&quot;550&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Users can choose not to share some properties. In this screenshot, the
user has unchecked the &#39;Phone numbers&#39; button. Even though the site
asked for phone numbers, they will not be shared with the site.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There&#39;s no option to bulk-select all contacts so that users are encouraged
to select only the contacts that they need to share for that particular
website. Users can also control which properties are shared with the site
by toggling the property button at the top of the picker.&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;h3 id=&quot;security-transparency&quot;&gt;Transparency &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-transparency&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To clarify which contact details are being shared, the picker always
shows the contact&#39;s name and icon, plus any properties that the site has
requested. For example, if a site requests &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;tel&lt;/code&gt;,
all three properties will be shown in the picker. Alternatively,
if a site only requests &lt;code&gt;tel&lt;/code&gt;, the picker will show only the name, and
telephone numbers.&lt;/p&gt;
&lt;div class=&quot;w-columns&quot;&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/contact-picker/contact-picker-left.jpg&quot; class=&quot;w-screenshot&quot; alt=&quot;Screen shot of picker for site requesting all properties.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Picker, site requesting &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and
&lt;code&gt;tel&lt;/code&gt;, one contact selected.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;w-figure&quot;&gt;
&lt;img src=&quot;https://web.dev/contact-picker/contact-picker-right.jpg&quot; class=&quot;w-screenshot&quot; alt=&quot;Screen shot of picker for site requesting only phone numbers.&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
Picker, site requesting only &lt;code&gt;tel&lt;/code&gt;, one contact selected.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;figure class=&quot;w-figure w-figure--inline-right&quot;&gt;
&lt;img src=&quot;https://web.dev/contact-picker/contact-picker-long-press.jpg&quot; class=&quot;w-screenshot&quot; alt=&quot;Screen shot of picker when a contact is long-pressed.&quot; width=&quot;550&quot;&gt;
&lt;figcaption class=&quot;w-figcaption&quot;&gt;
The result of a long press on a contact.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A long press on a contact will show all of the information that will be
shared if the contact is selected. (See the Cheshire Cat contact image.)&lt;/p&gt;
&lt;div class=&quot;w-clearfix&quot;&gt;&lt;/div&gt;
&lt;h3 id=&quot;security-persistence&quot;&gt;No permission persistence &lt;a class=&quot;w-headline-link&quot; href=&quot;#security-persistence&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Access to contacts is on-demand, and not persisted. Each time a site wants
access, it must call &lt;code&gt;navigator.contacts.select()&lt;/code&gt; with a user gesture,
and the user must individually choose the contact(s) they want to share
with the site.&lt;/p&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;w-headline-link&quot; href=&quot;#feedback&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chrome team wants to hear about your experiences with the Contact Picker
API.&lt;/p&gt;
&lt;h3 id=&quot;problem-with-the-implementation&quot;&gt;Problem with the implementation? &lt;a class=&quot;w-headline-link&quot; href=&quot;#problem-with-the-implementation&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you find a bug with Chrome&#39;s implementation? Or is the implementation
different from the spec?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File a bug at &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/entry?components=Blink%3EContacts&quot;&gt;https://new.crbug.com&lt;/a&gt;. Be sure to include as much
detail as you can, provide simple instructions for reproducing the bug, and
set &lt;em&gt;Components&lt;/em&gt; to &lt;code&gt;Blink&amp;gt;Contacts&lt;/code&gt;. &lt;a href=&quot;https://glitch.com/&quot;&gt;Glitch&lt;/a&gt; works great
for sharing quick and easy problem reproductions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;planning-to-use-the-api&quot;&gt;Planning to use the API? &lt;a class=&quot;w-headline-link&quot; href=&quot;#planning-to-use-the-api&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Are you planning to use the Contact Picker API? Your public support helps the
Chrome team to prioritize features, and shows other browser vendors how
critical it is to support them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Share how you plan to use it on the &lt;a href=&quot;https://discourse.wicg.io/t/proposal-contact-picker-api/3507&quot;&gt;WICG Discourse thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Send a Tweet to &lt;a href=&quot;https://twitter.com/chromiumdev&quot;&gt;@ChromiumDev&lt;/a&gt; with &lt;code&gt;#contactpicker&lt;/code&gt; and
let us know where and how you&#39;re using it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;helpful&quot;&gt;Helpful links &lt;a class=&quot;w-headline-link&quot; href=&quot;#helpful&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/contact-api/&quot;&gt;Public explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/contact-api/spec/&quot;&gt;Contact Picker Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://contact-picker.glitch.me/&quot;&gt;Contact Picker API Demo&lt;/a&gt; and &lt;a href=&quot;https://glitch.com/edit/#!/contact-picker?path=demo.js:20:0&quot;&gt;Contact Picker API demo source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=860467&quot;&gt;Tracking bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/features/6511327140904960&quot;&gt;ChromeStatus.com entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blink Component: &lt;code&gt;Blink&amp;gt;Contacts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;thanks&quot;&gt;Thanks &lt;a class=&quot;w-headline-link&quot; href=&quot;#thanks&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Big shout out and thanks to Finnur Thorarinsson and Rayan Kanso who are
implementing the feature and Peter Beverloo whose
&lt;a href=&quot;https://tests.peter.sh/contact-api/&quot;&gt;code&lt;/a&gt; I shamelessly
&lt;strike&gt;stole and&lt;/strike&gt; refactored for the demo.&lt;/p&gt;
&lt;p&gt;PS: The &#39;names&#39; in my contact picker, are characters from Alice in Wonderland.&lt;/p&gt;
</content>
<author>
<name>Pete LePage</name>
</author>
</entry>
<entry>
<title>Native lazy-loading for the web</title>
<link href="https://web.dev/native-lazy-loading/"/>
<updated>2019-08-05T17:00:00-07:00</updated>
<id>https://web.dev/native-lazy-loading/</id>
<content type="html">&lt;p&gt;Support for natively lazy-loading images and iframes is coming to the web! This video shows
a &lt;a href=&quot;https://mathiasbynens.be/demo/img-loading-lazy&quot;&gt;demo&lt;/a&gt; of the feature:&lt;/p&gt;
&lt;figure class=&quot;w-figure w-figure--fullbleed&quot;&gt;
&lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; class=&quot;w-screenshot&quot;&gt;
&lt;source src=&quot;https://web.dev/native-lazy-loading/lazyload.webm&quot; type=&quot;video/webm&quot;&gt;
&lt;source src=&quot;https://web.dev/native-lazy-loading/lazyload.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;Starting with Chrome 76, you&#39;ll be able to use the new &lt;code&gt;loading&lt;/code&gt; attribute to lazy-load resources
without the need to write custom lazy-loading code or use a separate JavaScript library. Let&#39;s dive
into the details.&lt;/p&gt;
&lt;h2 id=&quot;why-native-lazy-loading&quot;&gt;Why native lazy-loading? &lt;a class=&quot;w-headline-link&quot; href=&quot;#why-native-lazy-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;According to &lt;a href=&quot;https://httparchive.org/reports/page-weight&quot;&gt;HTTPArchive&lt;/a&gt;, images are the most
requested asset type for most websites and usually take up more bandwidth than any other
resource. At the 90th percentile, sites send about 4.7 MB of images on desktop and mobile. That&#39;s a
lot of &lt;a href=&quot;https://en.wikipedia.org/wiki/Cats_and_the_Internet&quot;&gt;cat pictures&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Embedded iframes also use a lot of data and can harm page performance. Only loading
non-critical, below-the-fold images and iframes when the user is likely to see them improves
page load times, minimizes user bandwidth, and reduces memory usage.&lt;/p&gt;
&lt;p&gt;Currently, there are two ways to defer the loading of off-screen images and iframes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the &lt;a href=&quot;https://developers.google.com/web/updates/2016/04/intersectionobserver&quot;&gt;Intersection Observer
API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;scroll&lt;/code&gt;, &lt;code&gt;resize&lt;/code&gt;, or &lt;code&gt;orientationchange&lt;/code&gt; &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/#using_event_handlers_the_most_compatible_way&quot;&gt;event
handlers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Either option can let developers include lazy-loading functionality, and many developers have built
third-party libraries to provide abstractions that are even easier to use. With lazy-loading
supported directly by the browser, however, there&#39;s no need for an external library. Native lazy
loading also ensures that deferred loading of images and iframes still works even if JavaScript is
disabled on the client.&lt;/p&gt;
&lt;h2 id=&quot;the-loading-attribute&quot;&gt;The &lt;code&gt;loading&lt;/code&gt; attribute &lt;a class=&quot;w-headline-link&quot; href=&quot;#the-loading-attribute&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Today, Chrome already loads images at different priorities depending on where they&#39;re located with
respect to the device viewport. Images below the viewport are loaded with a lower priority, but they&#39;re
still fetched as soon as possible.&lt;/p&gt;
&lt;p&gt;In Chrome 76, you can use the &lt;code&gt;loading&lt;/code&gt; attribute to completely defer the loading of offscreen images
and iframes that can be reached by scrolling:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;lazy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;200&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;200&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;iframe&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://example.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;lazy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;iframe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Here are the supported values for the &lt;code&gt;loading&lt;/code&gt; attribute:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto&lt;/code&gt;: Default lazy-loading behavior of the browser, which is the same as not
including the attribute.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lazy&lt;/code&gt;: Defer loading of the resource until it reaches a &lt;a href=&quot;#load-in-distance-threshold&quot;&gt;calculated distance&lt;/a&gt; from the viewport.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eager&lt;/code&gt;: Load the resource immediately, regardless of where it&#39;s located on the page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The feature will continue to be updated until it&#39;s launched in a stable release (Chrome 76 at the
earliest). But you can try it out by enabling the following flags in Chrome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chrome://flags/#enable-lazy-image-loading&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chrome://flags/#enable-lazy-frame-loading&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;load-in-distance-threshold&quot;&gt;Load-in distance threshold &lt;a class=&quot;w-headline-link&quot; href=&quot;#load-in-distance-threshold&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;All images and iframes that are above the fold—that is, immediately viewable without scrolling—load
normally. Those that are far below the device viewport are only fetched when the user scrolls near
them.&lt;/p&gt;
&lt;p&gt;The distance threshold is not fixed and varies depending on several factors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The type of resource being fetched (image or iframe)&lt;/li&gt;
&lt;li&gt;Whether &lt;a href=&quot;https://blog.chromium.org/2019/04/data-saver-is-now-lite-mode.html&quot;&gt;Lite mode&lt;/a&gt; is enabled
on Chrome for Android&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://googlechrome.github.io/samples/network-information/&quot;&gt;effective connection type&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find the default values for the different effective connection types in the &lt;a href=&quot;https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/frame/settings.json5?l=971-1003&amp;amp;rcl=e8f3cf0bbe085fee0d1b468e84395aad3ebb2cad&quot;&gt;Chromium
source&lt;/a&gt;.
These numbers, and even the approach of fetching only when a certain distance from the viewport is
reached, may change in the near future as the Chrome team improves heuristics to determine when to
begin loading.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;In Chrome 77, you can experiment with these different thresholds by &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/network/#throttle&quot;&gt;throttling the
network&lt;/a&gt; in DevTools. In
the meantime, you will need to override the effective connection type of the browser using the
&lt;code&gt;chrome://flags/#force-effective-connection-type&lt;/code&gt; flag.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;image-loading&quot;&gt;Image loading &lt;a class=&quot;w-headline-link&quot; href=&quot;#image-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To prevent the surrounding content from reflowing when a lazy-loaded image is downloaded, make sure
to add &lt;code&gt;height&lt;/code&gt; and &lt;code&gt;width&lt;/code&gt; attributes to the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element or specify their values directly in an
inline style:&lt;/p&gt;
&lt;web-copy-code&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;lazy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;200&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;200&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;lazy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style-attr language-css&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt; &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&quot;&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;200px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;200px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- lazy-loaded --&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/web-copy-code&gt;&lt;p&gt;Images will still lazy-load if dimensions are not included, but &lt;a href=&quot;https://www.youtube.com/watch?v=4-d_SoCHeWE&quot;&gt;specifying them decreases the chance of
browser reflow&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;w-aside w-aside--note&quot;&gt;
&lt;p&gt;Take a look at this &lt;a href=&quot;https://mathiasbynens.be/demo/img-loading-lazy&quot;&gt;demo&lt;/a&gt; to see how the &lt;code&gt;loading&lt;/code&gt; attribute works with 100 pictures.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id=&quot;iframe-loading&quot;&gt;iframe loading &lt;a class=&quot;w-headline-link&quot; href=&quot;#iframe-loading&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;loading&lt;/code&gt; attribute affects iframes differently than images, depending on whether the iframe is
hidden. (Hidden iframes are often used for analytics or communication purposes.) Chrome uses the
following criteria to determine whether an iframe is hidden:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The iframe&#39;s width and height are 4 px or smaller.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;display: none&lt;/code&gt; or &lt;code&gt;visibility: hidden&lt;/code&gt; is applied.&lt;/li&gt;
&lt;li&gt;The iframe is placed off-screen using negative X or Y positioning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If an iframe meets any of these conditions, Chrome considers it hidden and won&#39;t lazy-load it in
most cases. Iframes that &lt;em&gt;aren&#39;t&lt;/em&gt; hidden will only load when they&#39;re within the &lt;a href=&quot;#load-in-distance-threshold&quot;&gt;load-in distance threshold&lt;/a&gt;.
A placeholder shows for lazy-loaded iframes that are still being fetched.&lt;/p&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ &lt;a class=&quot;w-headline-link&quot; href=&quot;#faq&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;are-there-plans-to-expand-this-feature&quot;&gt;Are there plans to expand this feature? &lt;a class=&quot;w-headline-link&quot; href=&quot;#are-there-plans-to-expand-this-feature&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are plans to change the default lazy-loading behavior of the browser to automatically
lazy-load any images or iframes that are well suited to being deferred if &lt;a href=&quot;https://blog.chromium.org/2019/04/data-saver-is-now-lite-mode.html&quot;&gt;Lite
mode&lt;/a&gt; is enabled on Chrome for
Android.&lt;/p&gt;
&lt;h3 id=&quot;can-i-change-how-close-an-image-or-iframe-needs-to-be-before-a-load-is-triggered&quot;&gt;Can I change how close an image or iframe needs to be before a load is triggered? &lt;a class=&quot;w-headline-link&quot; href=&quot;#can-i-change-how-close-an-image-or-iframe-needs-to-be-before-a-load-is-triggered&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;These values are hardcoded and can&#39;t be changed through the API. However, they may change in the
future as the Chrome team experiments with different threshold distances and variables.&lt;/p&gt;
&lt;h3 id=&quot;can-css-background-images-take-advantage-of-the-loading-attribute&quot;&gt;Can CSS background images take advantage of the &lt;code&gt;loading&lt;/code&gt; attribute? &lt;a class=&quot;w-headline-link&quot; href=&quot;#can-css-background-images-take-advantage-of-the-loading-attribute&quot; aria-hidden=&quot;true&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;No, it can currently only be used with &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;h3 id=&quot;how-does-the-loading-attribute-work-with-images-that-are-in-the-viewport-but-not-immediately-visible-(for-example-behind-a-carousel)&quot;&gt;How does the &lt;code&gt;loading&lt;/code&gt; attribute work with images that are in the viewport but not immediately visible (for example, behind a carousel)? &lt;a class=&quot;w-headline-link&quot; href=&quot;#how-does-the-loading-attribute-work-wit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment