Quantcast
Channel: Updates
Viewing all 599 articles
Browse latest View live

Making touch scrolling fast by default

$
0
0

Making touch scrolling fast by default

We know that scrolling responsiveness is critical to the user's engagement with a website on mobile, yet touch event listeners often cause serious scrolling performance problems. Chrome has been addressing this by allowing touch event listeners to be passive (passing the {passive: true} option to addEventListener()) and shipping the pointer events API. These are great features to drive new content into models that don't block scrolling, but developers sometimes find them hard to understand and adopt.

We believe the web should be fast by default without developers needing to understand arcane details of browser behavior. In Chrome 56 we are defaulting touch listeners to passive by default in cases where that most often matches the developer's intention. We believe that by doing this we can greatly improve the user's experience whilst having minimal negative impact on sites.

In rare cases this change can result in unintended scrolling. This is usually easily addressed by applying a touch-action: none style to the element where scrolling shouldn't occur. Read on for details, how to know if you are impacted, and what you can do about it.

Background: Cancelable Events slow your page down

If you call preventDefault() in the touchstart or first touchmove events then you will prevent scrolling. The problem is that most often listeners will not call preventDefault(), but the browser needs to wait for the event to finish to be sure of that. Developer defined "passive event listeners" solve this. When you add a touch event with a {passive: true} object as the third parameter in your event handler then you are telling the browser that the "touchstart" listener will not call preventDefault() and the browser can safely perform the scroll without blocking on the listener. For example:

window.addEventListener("touchstart", func, {passive: true} );

The Intervention

Our main motivation is to reduce the time it takes to update the display after the user touches the screen. To understand the usage of touchstart and touchmove we added metrics to determine how frequently scroll blocking behaviour occurred.

We looked at the percentage of cancelable touch events that were sent to a root target (window, document, or body) and determined that about 80% of these listeners are conceptually passive but were not registered as such. Given the scale of this problem we noticed a great opportunity to improve scrolling without any developer action by making these events automatically "passive".

This drove us to define our intervention as: if the target of a touchstart or touchmove listener is the window, document or body we default passive to true. This means that code like:

window.addEventListener("touchstart", func);

becomes equivalent to:

window.addEventListener("touchstart", func, {passive: true} );

Now calls to preventDefault() inside the listener will be ignored.

The graph below shows the time taken by the top 1% of scrolls from the time a user touches the screen to scroll to the time the display is updated. This data is for all websites in Chrome for Android. Before the intervention was enabled 1% of scrolls took just over 400ms. That has now been reduced to just over 250ms in Chrome 56 Beta; a reduction of about 38%. In the future we hope to make passive true the default for all touchstart and touchmove listeners, reducing this to below 50ms.

Breakage and Guidance

In the vast majority of cases, no breakage will be observed. But when breakage does occur, the most common symptom is that scrolling happens when you don't want it. In rare cases, developers may also notice unexpected click events (when preventDefault() was missing from a touchend listener).

In Chrome 56 and later, DevTools will log a warning when you call preventDefault() in an event where the intervention is active.

touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

Your application can determine whether it may be hitting this in the wild by checking if calling preventDefault had any effect via the defaultPrevented property.

We've found that a large majority of impacted pages are fixed relatively easily by applying the touch-action CSS property whenever possible. If you wish to prevent all browser scrolling and zooming within an element apply touch-action: none to it. If you have a horizontal carousel consider applying touch-action: pan-y pinch-zoom to it so that the user can still scroll vertically and zoom as normal. Applying touch-action correctly is already necessary on browsers such as desktop Edge that support Pointer Events and not Touch Events. For mobile Safari and older mobile browsers that don't support touch-action your touch listeners must continue calling preventDefault even when it will be ignored by Chrome.

In more complex cases it may be necessary to also rely on one of the following:

  • If your touchstart listener calls preventDefault(), ensure preventDefault() is also called from associated touchend listeners to continue suppressing the generation of click events and other default tap behavior.
  • Last (and discouraged) pass {passive: false} to addEventListener() to override the default behavior. Be aware you will have to feature detect if the User Agent supports EventListenerOptions.

Conclusion

In Chrome 56 scrolling starts substantially faster on many websites. This is the only impact that most developers will notice as a result of this change. In some cases developers may notice unintended scrolling.

Although it's still necessary to do so for mobile Safari, websites should not rely on calling preventDefault() inside of touchstart and touchmove listeners as this is no longer guaranteed to be honored in Chrome. Developers should apply the touch-action CSS property on elements where scrolling and zooming should be disabled to notify the browser before any touch events occur. To suppress the default behavior of a tap (such as the generation of a click event), call preventDefault() inside of a touchend listener.


Web Components v1 - the next generation

$
0
0

Web Components v1 - the next generation

Ever wanted to build your own self-contained JavaScript component, that you can easily use across multiple projects or share with other developers regardless of what JavaScript framework they use? Web Components may be for you.

Web Components are a set of new web platform features that let you create your own HTML elements. Each new custom element can have a custom tag like <my-button>, and have all the goodness of built-in elements - custom elements can have properties and methods, fire and respond to events, and even have an encapsulated style and DOM trees to bring along their own look and feel.

animation of paper-progress element

By providing a platform-based, low-level component model, Web Components let you build and share re-usable elements for everything from specialized buttons to complex views like datepickers. Because Web Components are built with platform API’s which include powerful encapsulation primitives, you can even use these components within other JavaScript libraries or frameworks as if they were standard DOM elements.

You may have heard of Web Components before - an early version of the Web Components spec - v0 - was shipped in Chrome 33.

Today, thanks to broad collaboration between browser vendors, the next-generation of the Web Components spec - v1 - is gaining wide support. Chrome supports the two major specs that make up Web Components - Shadow DOM and Custom Elements

To define a new custom element using the v1 implementation, you simply create a new class that extends HTMLElement using ES6 syntax and register it with the browser:

class MyElement extends HTMLElement {...}
window.customElements.define('my-element', MyElement);

The new v1 specs are extremely powerful - we’ve put together tutorials on using Custom Elements v1 and Shadow DOM v1 to help you get started.

webcomponents.org

The Web Component community is also growing in leaps and bounds. We’re excited to see the launch of an updated webcomponents.org site - the focal point of the web components community - including an integrated catalog for developers to share their elements.

webcomponents.org

The webcomponents.org site contains information about the Web Components specs, updates and content from the web components community, and displays documentation for open-source elements and collections of elements built by other developers.

You can get started building your first element using a library like Google’s Polymer, or just use the low-level Web Component API’s directly. Then publish your element on webcomponents.org.

Happy componentizing!

New In Chrome 56

$
0
0

New In Chrome 56

  • Web apps can now communicate with nearby Bluetooth Low Energy devices using the Web Bluetooth API.
  • position: sticky is back - making it easy to create elements that scroll normally until sticking to the top of the viewport.
  • And HTML5 by Default is enabled for all users.

I’m Pete LePage, let’s dive in and see what’s new for developers in Chrome 56.

Web Bluetooth API

Until now, the ability to interact with bluetooth devices has only been possible for native apps. With Chrome 56, your web apps can communicate with nearby Bluetooth Low Energy devices in a secure and private manner using the Web Bluetooth API.

The Web Bluetooth API uses the GATT protocol, which enables your app to connect to devices such as light bulbs, toys, heart-rate monitors, LED displays and more with just a few lines of JavaScript. Web Bluetooth can also be combined with physical web beacons to discover and control nearby devices.

Francois has a great article on Updates, be sure to check out some of neat demos to go along with it. And be sure to check out the Web Bluetooth Community.

CSS position: sticky;

Previously, building content headers that scrolled normally until sticking to the top of the viewport required listening to scroll events and switching an element’s position from relative to fixed at a specified threshold. It was difficult to synchronize, and often results in small visual jumps.

Chrome now supports CSS position: sticky;, a new way to position elements.

An element that is position sticky, starts relative; but becomes fixed, after the element reaches a certain scroll position.

Simply set position: sticky, and set a threshold for it to become sticky.

h3 {
  /* Element will be 'fixed' when it ... */
  position: sticky;
  /* ... is 10px from the top of the viewport */
  top: 10px;
}

Paul Kinlan has an Updates post about it.

HTML5 By Default

Last August, we announced that we’d be moving to HTML5 By Default to offer a safer, more power-efficient experience. This change disables Adobe Flash Player unless there’s a user indication that they want Flash content on specific sites, and eventually all websites will require the user’s permission to run Flash.

In Chrome 56, HTML5 By Default has been enabled for all users, which means they will be prompted to run Flash on sites they've never visited.

More details about how and when users will be prompted, and recommendations on how to test your Flash sites.

And more

And of course, there’s plenty more.

If you want to stay up to date with Chrome and know what’s coming, be sure to subscribe, follow @ChromiumDev on Twitter and be sure to check out the videos from the Chrome Dev Summit for a deeper dive into some of the awesome things the Chrome team is working on.

I’m Pete LePage, and as soon as Chrome 57 is released, I’ll be right here to tell you -- what’s new in Chrome!

Subscribe to Chrome Developers on YouTube

Subscribe to our YouTube channel or our RSS feed

Oh, and a big thanks to Andrew for lending me his shirt! I had a bit of a wardrobe malfunction.

CSS Grid – Table layout is back. Be there and be square.

$
0
0

CSS Grid – Table layout is back. Be there and be square.

TL;DR: If you are familiar with Flexbox, Grid should feel familiar. Rachel Andrew maintains a great website dedicated to CSS Grid to help you get started. Grid is now available in Google Chrome.

Flexbox? Grid?

Over the past few years, CSS Flexbox has become widely used and browser support is looking really good (unless you are one of the poor souls that have to support IE9 and below). Flexbox made a lot of complex layout tasks easier, like equi-distant spacing between elements, top-to-bottom layouts or the holy grail of CSS wizardry: vertical centering.

There's no way to align elements across multiple flexbox containers.

But alas, screens commonly have a second dimension we need to worry about. Short of taking care of sizing the elements yourself, sadly, you can't have both a vertical and horizontal rhythm by just using flexbox alone. This is where CSS Grid comes to the rescue.

CSS Grid has been in development, behind a flag in most browsers for over 5 years and extra time has been spent on interoperability to avoid a buggy launch like Flexbox had. So if you use Grid to implement your layout in Chrome, chances are you'll get the same result in Firefox and Safari. At the time of writing, Microsoft's Edge implementation of Grid is out of date (the same as was already present in IE11.) and the update is "under consideration".

Despite the similarities in concept and syntax, don't think of Flexbox and Grid as competing layout techniques. Grid arranges in two dimensions, while Flexbox lays out in one. There is synergy when using the two together.

Defining a grid

To get familiar with the individual properties of Grid I heartily recommend Rachel Andrew’s Grid By Example or CSS Tricks' Cheat Sheet. If you are familiar with Flexbox, a lot of the properties and their meaning should be familiar.

Let's take a look at a standard 12-column grid layout. The classic 12-column layout is popular as the number 12 is divisible by 2, 3, 4 and 6, and is therefore useful for many designs. Let's implement this layout:

There's no way to align elements across multiple flexbox containers.

Let's start with our markup code:

<!DOCTYPE html>
<body>
  <header></header>
  <nav></nav>
  <main></main>
  <footer></footer>
</body>

In our stylesheet we start by expanding our body so it covers the entire viewport and turning it into a grid container:

html, body {
  width: 100vw;
  min-height: 100vh;
  margin: 0;
  padding: 0;
}
body {
  display: grid;
}

Now we are using CSS Grid. Hooray!

The next step is to implement the rows and columns of our grid. We could implement all 12 columns in our mockup, but as we are not using every column, doing this would make our CSS unnecessarily messy. For the sake of simplicity, we'll implement the layout this way:

The header and the footer are variable in width and the content is variable in both dimensions. The nav will be variable in both dimensions as well, but we are going to impose a minimum width of 200px on it. (Why? To show off the features of CSS Grid, of course.)

In CSS Grid, the set of columns and rows are called tracks. Let's start with defining our first set of tracks, the rows:

body {
  display: grid;
  grid-template-rows: 150px auto 100px;
}

grid-template-rows takes a sequence of sizes that define the individual rows. In this case, we give the first row a height of 150px and the last one of 100px. The middle row is set to auto which means it will adjust to the necessary height to accommodate the grid items (the children of the grid container) in that row. Since our body is stretched across the entire viewport, the track containing the content (yellow in the picture above) will at least fill all available space, but will grow (and make the document scroll) if that's necessary.

For the columns we want to take a more dynamic approach: we want both nav and content to grow (and shrink), but we want nav to never shrink below 200px and we want content to be larger than nav. In Flexbox we would use flex-grow and flex-shrink, but in Grid it's a little different:

body {
  display: grid;
  grid-template-rows: 150px auto 100px;
  grid-template-columns: minmax(200px, 3fr) 9fr;
}

We are defining 2 columns. The first column is defined using the minmax() function, which takes 2 values: The minimum and the maximum size of that track. (It's like min-width and max-width in one.) The minimum width is, as we discussed before, 200px. The maximum width is 3fr. fr is a grid-specific unit that allows you distribute available space to the grid elements. (fr probably stands for "fraction unit", but might also mean "free unit" soon.) Our values here mean that both columns will grow to fill the screen, but the content column will always be 3 times as wide as the nav column (provided the nav column stays wider than 200px).

While the placement of our grid items is not correct yet, the size of the rows and columns behaves correctly and yields the behavior we were aiming for:

Placing the items

One of the most powerful features of Grid is to be able to place items without regard to the DOM order. (Although, because screen readers navigate the DOM, we highly recommend that to be properly accessible you should be mindful of how you reorder elements.) If no manual placement is done, the elements are placed in the Grid in DOM order, arranged left to right and top to bottom. Each element occupies one cell. The order in which the grid is filled can be changed using grid-auto-flow.

So, how do we place elements? Arguably the easiest way to place grid items is by defining which columns and rows they cover. Grid offers two syntaxes to do this: In the first syntax you define start and end points. In the second one you define a start point and a span:

header {
  grid-column: 1 / 3;
}
nav {
  grid-row: 2 / span 2;
}

We want our header to start in the first column and end before the 3rd column. Our nav should start in the second row and span 2 rows in total.

Technically, we are done implementing our layout, but I want to show you a few convenience features that Grid provides for you to make placement easier. The first feature is that you can name your track borders and use those names for placement:

body {
  display: grid;
  grid-template-rows: 150px [nav-start] auto 100px [nav-end];
  grid-template-columns: [header-start] minmax(200px, 3fr) 9fr [header-end];
}
header {
  grid-column: header-start / header-end;
}
nav {
  grid-row: nav-start / nav-end;
}

The code above will yield the same layout as the code before.

Even more powerful is the feature of naming entire regions in your grid:

body {
  display: grid;
  grid-template-rows: 150px auto 100px;
  grid-template-columns: minmax(200px, 3fr) 9fr;
  grid-template-areas: "header header"
                      "nav    content"
                      "nav    footer";
}
header {
  grid-area: header;
}
nav {
  grid-area: nav;
}

grid-template-areas takes a string of space-separated names, allowing the developer to give each cell a name. If two adjacent cells have the same name, they are going to be coalesced to the same area. This way you can provide more semantics to your layout code and make media queries more intuitive. Again, this code will generate the same layout as before.

Is there more?

Yes, yes there is, way too much to cover in a single blog post. Rachel Andrew, who is also a GDE, is an Invited Expert in the CSS Working Group and has been working with them from the very start to make sure Grid simplifies web design. She even wrote a book on it. Her website Grid By Example is a valuable resource for getting familiar with Grid. Many people think Grid is a revolutionary step for web design and it is now enabled by default in Chrome so you can start using it today.

Changes in the Payment Request API

$
0
0

Changes in the Payment Request API

Since the launch of the Payment Request API in Chrome 53, a few changes have been made to the API. These changes won't break the functionalities of your working code, but we recommend you to add a shim to your code so that future changes won't break your product.

Note: All changes described here are already reflected in the existing integration guides.

Chrome 57

PaymentRequest is now available inside iframes

The Payment Request API can now be called from within an iframe by adding the allowpaymentrequest attribute to the iframe element.

<iframe src="/totally/fake/url" allowpaymentrequest></iframe>

PaymentMethodData supports "basic-card"

The first argument to PaymentRequest() constructor is an array of payment method data. The PaymentMethodData format has been altered in this release.

Old - still works for the time being.

var methodData = [{
  supportedMethods: ['visa', 'mastercard', 'amex', 'jcb']
}];
var request = new PaymentRequest(methodData, details, options);

New - the new structure.

var methodData = [{
  supportedMethods: ['basic-card'],
  data: {
    supportedNetworks: ['visa', 'mastercard', 'amex', 'jcb',
      'diners', 'discover', 'mir', 'unionpay']
  }
}];
var request = new PaymentRequest(methodData, details, options);

The format of the data property depends on the value in supportedMethods and is based on the Basic Card specification. Note that the spec includes supportedTypes which accepts credit, debit or prepaid, but Chrome 57 ignores this property and treats any values in supoprtedNetworks as credit cards.

var methodData = [{
  supportedMethods: ['basic-card'],
  data: {
    supportedNetworks: ['visa', 'mastercard', 'amex', 'jcb',
      'diners', 'discover', 'mir', 'unionpay'],
    supportedTypes: ['credit'] <= not available
  }
}];

Chrome 56

pending

As part of payment item information, developers can add pending to indicate that the price is not fully determined yet. The pe nding fieldaccepts a boolean value.

{
  label: "State tax",
  amount: { currency: "USD", value : "5.00" },
  pending: true
},

This is commonly used to show line items such as shipping or tax amounts that depend on selection of shipping address or shipping options. Chrome indicates pending fields in the UI for the payment request.

requestPayerName

As part of shipping option (third argument to PaymentRequest), developers can now add requestPayerName to request the payer's name separate from shipping address information. requestPayerName accepts a boolean value.

shippingType

As part of shipping option (third argument to PaymentRequest), developers can now add shippingType to request that the UI show "delivery" or "pickup" instead of "shipping". shippingType accepts the strings shipping (default), delivery, or pickup.

shippingType="delivery"
shippingType="delivery"
shippingType="pickup"
shippingType="pickup"

Serializer functions available to PaymentResponse and PaymentAddress

PaymentResponse object and PaymentAddress object are now JSON-serializable. Developers can convert one to a JSON object by calling the toJSON() function and avoid creating cumbersome toDict() functions.

request.show().then(response => {
  let res = response.toJSON();
});

canMakePayment

In addition to the API availability, you can check to see if a user has an active payment method before invoking the Payment Request API. Remember that this is optional as users can still add a new payment method on the payment UI.

let request = new PaymentRequest(methods, details, options);
if (request.canMakePayment) {
  request.canMakePayment().then(result => {
    if (result) {
      // Payment methods are available.
    } else {
      // Payment methods are not available, but users can still add
      // a new payment method in Payment UI.
    }
  }).catch(error => {
    // Unable to determine.
  });
}

API Deprecations and Removals in Chrome 57

$
0
0

API Deprecations and Removals in Chrome 57

In nearly every version of Chrome, we see a significant number of updates and improvements to the product, its performance, and also capabilities of the Web Platform. This article describes the deprecations and removals in Chrome 57, which is in beta as of early February. This list is subject to change at any time.

Deprecate case-insensitive matching for usemap attribute

The usemap attribute was formerly defined as caseless. Unfortunately implementing this was complicated enough that no browsers implemented it correctly. Research suggested that such complicated algorithm is unnecessary, and even ASCII case-insensitive matching is unnecessary.

Consequently, the specification was updated so that case-sensitive matching is applied. The old behavior is deprecated in Chrome 57, with removal expected in Chrome 58.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Deprecate and remove webkitCancelRequestAnimationFrame

The webkitCancelRequestAnimationFrame() method is a an obsolete, vendor-specific API and the standard cancelAnimationFrame() has long been supported in Chromium. Therefore the webkit version is being removed.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Prefixed resource timing buffer-management API (removed)

Two methods and an event handler, webkitClearResourceTimings(), webkitSetResourceTimingBufferSize(), and onwebkitresourcetimingbufferfull are obsolete and vendor-specific. The standard versions of these APIs have been supported in since Chrome 46. These features were originally implemented in WebKit, but Safari has not enabled them. Firefox, IE 10+, and Edge have only unprefixed version of the API. Therefore the webkit versions are being removed.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Remove BluetoothDevice.uuids attribute

The BluetoothDevice.uuids attribute is being removed to bring the Bluetooth API in line with the current specification. You can retrieve a UUID by calling device.watchAdvertisements().

Chromium Bug

Remove key generation element

Since Chrome 49, <keygen>'s default behaviour has been to return the empty string, unless a permission was granted to this page. IE/Edge do not support <keygen> and have not indicated public signals to support <keygen>. Firefox already gates <keygen> behind a user gesture, but is publicly supportive of removing it. Safari ships <keygen> and has not expressed public views regarding its continued support. With Chrome 57, this element is removed.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Remove webkit-prefixed IndexedDB global aliases

The IndexedDB entry point and global constructors were exposed with webkit prefixes somewhere around Chrome 11. The non-prefixed versions were added in Chrome 24 and the prefixed versions were deprecated in Chrome 38. The following interfaces are affected:

  • webkitIndexedDB (main entry point)
  • webkitIDBKeyRange (non-callable global constructor, but has useful static methods)
  • webkitIDBCursor
  • webkitIDBDatabase
  • webkitIDBFactory
  • webkitIDBIndex
  • webkitIDBObjectStore
  • webkitIDBRequest
  • webkitIDBTransaction (non-callable global constructors)

Intent to Remove | Chromestatus Tracker | Chromium Bug

WebAudio: Remove prefixed AudioContext and OfflineAudioContext

Chrome has supported WebAudio since mid 2011, including AudioContext. OfflineAudioContext was added the following year. Given how long the standard interfaces and Googles long-term goal of removing prefixed features, the prefixed versions of these interfaces have been deprecated since late 2014 and are now being removed.

Intent to Remove | Chromestatus Tracker | Chromium Bug

<<../../_deprecation-policy.md>>

Customize Media Notifications and Handle Playlists

$
0
0

Customize Media Notifications and Handle Playlists

With the brand new Media Session API, you can now customize media notifications by providing metadata for the media your web app is playing. It also allows you to handle media related events such as seeking or track changing which may come from notifications or media keys. Excited? Try out the official Media Session sample.

The Media Session API is supported in Chrome 57 (beta in February 2017, stable in March 2017).

Media Session TL;DR;
Photo by Michael Alø-Nielsen / CC BY 2.0

Gimme what I want

You already know about the Media Session API and are simply coming back to copy and paste with no shame some boilerplate code? So here it is.

if ('mediaSession' in navigator) {

  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
      { src: 'https://dummyimage.com/96x96',   sizes: '96x96',   type: 'image/png' },
      { src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png' },
      { src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png' },
      { src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png' },
      { src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png' },
      { src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png' },
    ]
  });

  navigator.mediaSession.setActionHandler('play', function() {});
  navigator.mediaSession.setActionHandler('pause', function() {});
  navigator.mediaSession.setActionHandler('seekbackward', function() {});
  navigator.mediaSession.setActionHandler('seekforward', function() {});
  navigator.mediaSession.setActionHandler('previoustrack', function() {});
  navigator.mediaSession.setActionHandler('nexttrack', function() {});
}

Get into the code

Let's play 🎷

Add a simple <audio> element to your web page and assign several media sources so that the browser can choose which one works best.

<audio controls>
  <source src="audio.mp3" type="audio/mp3"/>
  <source src="audio.ogg" type="audio/ogg"/>
</audio>

Note: I could have used a <video> element instead to showcase the Media Session API.

As you may know, autoplay is disabled for audio elements on Chrome for Android which means we have to use the play() method of the audio element. This method must be triggered by a user gesture such as a touch or a mouse click. That means listening to pointerup, click, and touchend events. In other words, the user must click a button before your web app can actually make noise.

playButton.addEventListener('pointerup', function(event) {
  let audio = document.querySelector('audio');

  // User interacted with the page. Let's play audio...
  audio.play()
  .then(_ => { /* Set up media session... */ })
  .catch(error => { console.log(error) });
});

Note: If the <audio> element has the controls attribute, you can simply set up the media session in the audio play event listener instead which occurs when user taps the "play" audio control.

If you don't want to play audio right after the first interaction, I recommend you use the load() method of the audio element. This is one way for the browser to keep track of whether the user interacted with the element. Note that it may also help smooth the playback because the content will already be loaded.


let audio = document.querySelector('audio');

welcomeButton.addEventListener('pointerup', function(event) {
  // User interacted with the page. Let's load audio...
  audio.load()
  .then(_ => { /* Show play button for instance... */ })
  .catch(error => { console.log(error) });
});

// Later...
playButton.addEventListener('pointerup', function(event) {
  audio.play()
  .then(_ => { /* Set up media session... */ })
  .catch(error => { console.log(error) });
});

Customize the notification

When your web app is playing audio, you can already see a media notification sitting in the notification tray. On Android, Chrome does its best to show appropriate information by using the document's title and the largest icon image it can find.

Without media session
Without media session
With media session
With media session

Set metadata

Let's see how to customize this media notification by setting some media session metadata such as the title, artist, album name, and artwork with the Media Session API.

// When audio starts playing...
if ('mediaSession' in navigator) {

  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
      { src: 'https://dummyimage.com/96x96',   sizes: '96x96',   type: 'image/png' },
      { src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png' },
      { src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png' },
      { src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png' },
      { src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png' },
      { src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png' },
    ]
  });
}

Once playback is done, you don't have to "release" the media session as the notification will automatically disappear. Keep in mind that current navigator.mediaSession.metadata will be used when any playback starts. This is why you need to update it to make sure you're always showing relevant information in the media notification.

Previous track / next track

If your web app provides a playlist, you may want to allow the user to navigate through your playlist directly from the media notification with some "Previous Track" and "Next Track" icons.

let audio = document.createElement('audio');

let playlist = ['audio1.mp3', 'audio2.mp3', 'audio3.mp3'];
let index = 0;

navigator.mediaSession.setActionHandler('previoustrack', function() {
  // User clicked "Previous Track" media notification icon.
  index = (index - 1 + playlist.length) % playlist.length;
  playAudio();
});

navigator.mediaSession.setActionHandler('nexttrack', function() {
  // User clicked "Next Track" media notification icon.
  index = (index + 1) % playlist.length;
  playAudio();
});

function playAudio() {
  audio.src = playlist[index];
  audio.play()
  .then(_ => { /* Set up media session... */ })
  .catch(error => { console.log(error); });
}

playButton.addEventListener('pointerup', function(event) {
  playAudio();
});

Note that media action handlers will persist. This is very similar to the event listener pattern except that handling an event means that the browser stops doing any default behaviour and uses this as a signal that your web app supports the media action. Hence, media action controls won't be shown unless you set the proper action handler.

By the way, unsetting a media action handler is as easy as assigning it to null.

Seek backward / seek forward

The Media Session API allows you to show "Seek Backward" and "Seek Forward" media notification icons if you want to control the amount of time skipped.

let skipTime = 10; // Time to skip in seconds

navigator.mediaSession.setActionHandler('seekbackward', function() {
  // User clicked "Seek Backward" media notification icon.
  audio.currentTime = Math.max(audio.currentTime - skipTime, 0);
});

navigator.mediaSession.setActionHandler('seekforward', function() {
  // User clicked "Seek Forward" media notification icon.
  audio.currentTime = Math.min(audio.currentTime + skipTime, audio.duration);
});

Play / pause

The "Play/Pause" icon is always shown in the media notification and the related events are handled automatically by the browser. If for some reason the default behaviour doesn't work out, you can still handle "Play" and "Pause" media events.

navigator.mediaSession.setActionHandler('play', function() {
  // User clicked "Play" media notification icon.
  // Do something more than just playing current audio...
});

navigator.mediaSession.setActionHandler('pause', function() {
  // User clicked "Pause" media notification icon.
  // Do something more than just pausing current audio...
});

Note: The browser may consider that the web app is not playing media when files are seeking or loading. You can override this behaviour by setting navigator.mediaSession.playbackState to "playing" or "paused". This comes in handy when you want to make sure your web app UI stays in sync with the media notification controls.

Notifications everywhere

The cool thing about the Media Session API is that the notification tray is not the only place where media metadata and controls are visible. The media notification is synced automagically to any paired wearable device. And it also shows up on lock screens.

Lock Screen
Lock screen
Wear Notification
Wear Notification

Make it play nice offline

I know what you're thinking now... service worker to the rescue!

True but first and foremost, you want to make sure all items in this checklist are checked:

  • All media and artwork files are served with the appropriate Cache-Control HTTP header. This will allow the browser to cache and reuse previously fetched resources. See the Caching checklist.
  • Make sure all media and artwork files are served with the Allow-Control-Allow-Origin: * HTTP header. This will allow third-party web apps to fetch and consume HTTP responses from your web server.

The service worker caching strategy

Regarding media files, I recommend a simple "Cache, falling back to network" strategy as illustrated by Jake Archibald.

For artwork though, I'd be a little bit more specific and choose the approach below:

  • If artwork is already in the cache, serve it from the cache
  • Else fetch artwork from the network
    • If fetch is successful, add network artwork to the cache and serve it
    • Else serve the fallback artwork from the cache

That way, media notifications will always have a nice artwork icon even when browser can't fetch them. Here's how you could implement this:

const FALLBACK_ARTWORK_URL = 'fallbackArtwork.png';

self.addEventListener('install', event => {
  self.skipWaiting();
  event.waitUntil(initArtworkCache());
});

function initArtworkCache() {
  caches.open('artwork-cache-v1')
  .then(cache => cache.add(FALLBACK_ARTWORK_URL));
}

self.addEventListener('fetch', event => {
  if (/artwork-[0-9]+\.png$/.test(event.request.url)) {
    event.respondWith(handleFetchArtwork(event.request));
  }
});

function handleFetchArtwork(request) {
  // Return cache request if it's in the cache already, otherwise fetch
  // network artwork.
  return getCacheArtwork(request)
  .then(cacheResponse => cacheResponse || getNetworkArtwork(request));
}

function getCacheArtwork(request) {
  return caches.open('artwork-cache-v1')
  .then(cache => cache.match(request));
}

function getNetworkArtwork(request) {
  // Fetch network artwork.
  return fetch(request)
  .then(networkResponse => {
    if (networkResponse.status !== 200) {
      return Promise.reject('Network artwork response is not valid');
    }
    // Add artwork to the cache for later use and return network response.
    addArtworkToCache(request, networkResponse.clone())
    return networkResponse;
  })
  .catch(error => {
    // Return cached fallback artwork.
    return getCacheArtwork(new Request(FALLBACK_ARTWORK_URL))
  });
}

function addArtworkToCache(request, response) {
  return caches.open('artwork-cache-v1')
  .then(cache => cache.put(request, response));
}

Caution: If you want your service worker to be able to intercept artwork network requests on the very first page load, you may want to call clients.claim() within your service worker once it's activated.

Let user control cache

As the user consumes content from your web app, media and artwork files may take a lot of space on their device. It is your responsibility to show how much cache is used and give users the ability to clear it. Thankfully for us, doing so is pretty easy with the Cache API.

// Here's how I'd compute how much cache is used by artwork files...
caches.open('artwork-cache-v1')
.then(cache => cache.matchAll())
.then(responses => {
  let cacheSize = 0;
  let blobQueue = Promise.resolve();

   responses.forEach(response => {
    let responseSize = response.headers.get('content-length');
    if (responseSize) {
      // Use content-length HTTP header when possible.
      cacheSize += Number(responseSize);
    } else {
      // Otherwise, use the uncompressed blob size.
      blobQueue = blobQueue.then(_ => response.blob())
          .then(blob => { cacheSize += blob.size; blob.close(); });
    }
  });

  return blobQueue.then(_ => {
    console.log('Artwork cache is about ' + cacheSize + ' Bytes.');
  });
})
.catch(error => { console.log(error); });

// And here's how to delete some artwork files...
const artworkFilesToDelete = ['artwork1.png', 'artwork2.png', 'artwork3.png'];

caches.open('artwork-cache-v1')
.then(cache => Promise.all(artworkFilesToDelete.map(artwork => cache.delete(artwork))))
.catch(error => { console.log(error); });

Implementation notes

  • Chrome for Android requests "full" audio focus to show media notifications only when the media file duration is at least 5 seconds.
  • If no artwork is defined and there is an icon image at a desirable size, media notifications will use it.
  • Notification artwork size in Chrome for Android is 512x512. For low-end devices, it is 256x256.
  • Dismiss media notifications with audio.src = ''.
  • As the Web Audio API doesn't request Android Audio Focus for historical reasons, the only way to make it work with the Media Session API is to hook up an <audio> element as the input source to the Web Audio API. Hopefully, the proposed Web AudioFocus API will improve the situation in the near future.

Support

At the time of writing, Chrome for Android is the only platform that supports the Media Session API. More up-to-date information on browser implementation status can be found on Chrome Platform Status.

Samples & demos

Check out our official Chrome Media Session sample featuring Jan Morgenstern's work.

Resources

The New and Improved Add to Home screen

$
0
0

The New and Improved Add to Home screen

Chrome first introduced the "Add to Home screen" banners in Chrome 51. This was a big step for the web as it provided users the ability to easily keep a favorite site on their Home screen, much like native apps. We've heard from developers like Alibaba that users re-engage 4 times more often with their site added to Home screen. We've also seen that tuning the heuristics for add to Home screen to prompt sooner yields to 48% more installs.

We are happy to share that the team has worked on an improved add to Home screen experience that makes web apps first-class citizens of Android. Instead of simply being a shortcut icon, web apps will now be integrated with Android. This means that users that add a PWA to their Home screen will be able to find it anywhere they see other apps (e.g. in the app drawer or searching for apps), and open the site from intents. We see this as the first step among a number of improvements to come and intend to make it the default experience for add to Home screen in the coming months.

The improved add to Home screen experience is already available in Chrome Canary and will be rolling out to Chrome 57 beta over the next few weeks.

You can test your site by following these steps:

  1. Install the latest Chrome Dev or Chrome Beta from the Play Store if you don't have it already.
  2. Enable improved add to Home screen. These steps are only needed during the developer preview.
    1. Open chrome://flags in Chrome dev and enable the flag #enable-improved-a2hs ("Find in page" in the triple-dot menu is helpful for finding it.) You'll be prompted to restart Chrome.
    2. Once Chrome is restarted, you'll be prompted to go to settings to turn on "Unknown sources" if it's not enabled already. (In general you shouldn't have this enabled, so we recommend disabling it when you're done testing.) If you don't see the prompt you can find it in Android Settings > Security > Device Administration.
  3. Visit your PWA. You can start install from the three dot menu > "Add to Home screen" or through the Add to Home screen banner.
    1. In the developer preview you'll additionally see a prompt from the package installer to confirm.

This new experience is a huge improvement over the original version of Add to Home screen, but there are some differences between these installed Progressive Web Apps and Android Apps.

Updating your App's icon and name

You now have the ability to update your Progressive Web App's icon and name and have it reflected to the user. Changing your icon or name in the manifest will update the icon on the Home screen after the user has subsequently opened the site.

Android Intent Filters

When a Progressive Web App is installed via this new Improved Add to Home screen experience it will be registered with the system to be a target for the URL space for its domain. This means that the when a user clicks on a link that is contained within the scope of your Progressive Web App, your app will be opened up instead of Chrome with your PWA running.

The scope is property in the Web App manifest and it defaults to the origin. You can set it to an path that is relative to your origin and subsequently when a user navigates to a URL contained by the scope your installed Progressive Web App will be open.

Note: directly navigating to your site from the address bar will work exactly the same as it does for native apps that have an intent filter, Chrome assumes that the user intended to visit the site and will open the site.

Managing Permissions

By Installing your Progressive Web App it now becomes part of the system. Added sites show up on the Home screen, app drawer and throughout the Android System-UI as a user would expect. Permissions are handled differently, by default your app can only have the same permissions surface as Chrome would normally have when installed - you can't ask for Camera access at install time for example. This means that as developer you must request permission for sensitive API's such as Camera and Microphone access, notifications etc at runtime as you would for any normal web site and the Chrome runtime will prompt you for access.

Android normally gives instant access notifications, Installed Progressive Web Apps do not have this permission granted by default and your user must explicitly opt-in to receiving notifications

Storage and App State

When the user adds your Progressive Web App to their system Chrome will use the same profile and will not segregate the data. This means your service worker will already be installed, your cookies still active any client-side storage will be still stored the next time that the user opens the App.

This can cause some issues because if the user clears the Chrome profile, then your data in your app will also be cleared. To ensure that your user data is held more permanently, please use the Persistent Storage API.

Please let us know if you have any feedback or questions. If you encounter a bug, you can file it on the Chromium bug tracker here%0A(2)%0A(3)%0A%0AExpected%20result%3A%0A%0A%0AActual%20result%3A%0A). Please also take a look at FAQs below that aim to answer any additional questions you might have.


FAQs

What are the requirements for a site to use improved add to Home screen?

The requirements are designed to be the same as the technical requirements for the add to Home screen banner.

We recommend using Lighthouse to audit your PWA.

Note though that there is no engagement threshold for improved add to Home screen from the menu.

Does this change the triggering of the add to Home screen banner?

Improved add to Home screen does not itself change the triggering or behavior of the banner. Nevertheless, Chrome has recently lowered the site-engagement threshold for the banner to trigger and is constantly experimenting with improvements to this system. (See the keynote from Chrome Dev Summit.)

What happens to users who have already added a site to their Home screen?

Users will continue to get the existing add to Home screen experience, though if they add it again manually via the menu button, the new icon will use improved add to Home screen.

What will happen to the current add to Home screen experience?

Improved add to Home screen will replace add to Home screen for PWAs. There is no change to the existing functionality of add to Home screen for non-PWAs.

What happens if the user has already installed the native app for the site?

Like add to Home screen today, users will be able to add a site independent of any native apps. If you expect users to potentially install both, we recommend differentiating the icon or name of your site from your native app.

When a user opens a site installed via improved add to Home screen, will Chrome be running?

Yes, once the site is opened from the Home screen the primary activity is still Chrome. Cookies, permissions, and all other browser state will be shared.

Will my installed site's storage be cleared if the user clears Chrome's cache?

Yes.

Will I get auto granted Push Notifications permissions like I do in Android?

No, but we intend to experiment with this at a later date. (See the keynote from Chrome Dev Summit.)

Will I be able to register to handle custom URL schemes and protocols?

No.

Will I be able to register to open files of a specific type (i.e, PDF)?

No, but we are exploring that independently with the Web Share Target API.

How are permissions handled? Will I see the Chrome prompt or Android's?

Permissions will still be managed through Chrome. Users will see the Chrome prompts to grant permissions and will be able to edit them in Chrome settings.

What versions of Android will this work on?

The feature is supported wherever Chrome is, back to Android Jelly Bean.

Does this use the WebView?

No, the site opens in the version of Chrome the user added the site from.

If I update my site's service worker, will this update automatically in the background even without the user visiting the site?

No. The update to the SW will be processed the next time that the user visits the page.


Lighthouse January 2017 update

$
0
0

Lighthouse January 2017 update

Lighthouse is an open-source, automated tool for improving the quality of your web apps. You can install it as a [Chrome Extension][crx] or run it as a Node command line tool. When you give Lighthouse a URL, it runs a barrage of tests against the page and then generates a report explaining how well the page did and indicating areas for improvement.

Lighthouse Logo

Today, we're happy to announce the 1.5 release of Lighthouse, a huge release, with over 128 PRs. Lighthouse 1.5 includes a bunch of big new features, audits, and the usual bug fixes. You can install it from npm (npm i -g lighthouse) or download the extension from the Chrome Web Store.

New Audits

The CSS Usage Audit reports the number of unused style rules in your page and the cost/time savings of removing them:

CSS Usage Audit

The Optimized Images Audit reports images that are unoptimized and the cost/time savings of optimizing them:

Optimized Images AudiT

The Responsive Images Audit reports images that are too big and the potential cost/time savings of sizing them correctly for the given device:

Responsive Images Audit

The Deprecations and Interventions Audit lists console warnings from Chrome if your page is using deprecated APIs or features that have interventions:

Deprecations and Interventions Audit

Report changes

As you've seen, we've focused on making the report less visually cluttered by adding tabular data, hiding extraneous help text, and adding new features to make it easier to navigate the data.

Emulation settings

It's easy to forget what throttling and emulation settings were used for a particular Lighthouse run. Lighthouse reports now include the runtime emulation settings that were used to create the report; a long time feature request:

Emulation settings

More useful trace data

Lighthouse metrics like "First meaningful paint", "Time to Interactive", etc are mocked out as User Timing measures and injected back into the trace data saved with the --save-assets flag.

If you use the --save-assets flag, you can now drop the trace into DevTools or open it in the DevTools Timeline Viewer. You'll be able to see your key metrics in context with the full trace of the page load.

Trace data

Lighthouse Viewer

In HTML reports, you'll notice a new button with options for exporting the report in different formats. One of those options is "Open in Viewer". Clicking this button will send the report to the online Viewer, where you can further share the report with GitHub users.

Open in Viewer button

Open in Viewer result

Behind the scenes, Viewer gets your permission via OAuth to create a GitHub secret gist and saves the report there. Since it's done as your Gist, you maintain full control over the sharing of the report and you can delete it at any time. You can revoke the Viewer's permission to create gists under your GitHub settings.

Performance Experiment

The first version of the Performance Experiment project has landed in 1.5.0. This lets you experiment with your page load performance, interactively testing the effects of blocking or delaying assets in your critical path during development.

When Lighthouse is run with the --interactive flag, a special report is generated that allows interactive selection of costly page resources. The experiment server then reruns Lighthouse on that page with those resources blocked.

Toggling runtime settings

Learn more about the Performance Experiment in Lighthouse.

New Documentation

Last but not least, we've modernized the documentation at developers.google.com/web/tools/lighthouse/ and added new audit references.

New documentation

That's it for now!

For all the details on the latest in Lighthouse, see the full release notes over on Github. As always, hit us up to report bugs, file feature requests, or brainstorm ideas on what you'd like to see next.

Speed up Service Worker with Navigation Preloads

$
0
0

Speed up Service Worker with Navigation Preloads

TL;DR

The problem

When you navigate to a site that uses a service worker to handle fetch events, the browser asks the service worker for a response. This involves booting up the service worker (if it isn't already running), and dispatching the fetch event.

The bootup time depends on the device and conditions. It's usually around 50ms. On mobile it's more like 250ms. In extreme cases (slow devices, CPU in distress) it can be over 500ms. However, since the service worker stays awake for a browser-determined time between events, you only get this delay occasionally, such as when the user navigates to your site from a fresh tab, or another site.

The boot-up time isn't a problem if you're responding from the cache, as the benefit of skipping the network is greater than the boot-up delay. But if you're responding using the network…

SW boot
Navigation request

…the network request is delayed by the service worker booting-up.

We're continuing to reduce the boot-up time by using code-caching in V8, by skipping service workers that don't have a fetch event, by launching serivce workers speculatively, and other optimisations. However, bootup time will always be greater than zero.

Facebook brought the impact of this problem to our attention, and asked for a way to perform navigation requests in parallel:

SW boot
Navigation request

…and we said "yeah, seems fair".

"Navigation preload" to the rescue

Navigation preload is a new experimental feature that lets you say, "Hey, when the user makes a GET navigation request, start the network request while the service worker is booting up.

The startup delay is still there, but it doesn't block the network request, so the user gets content sooner.

Here's a video of it in action, where the service worker is given a deliberate 500ms startup delay using a while-loop:

Here's the demo itself. To get the benefits of navigation preload, you'll need Chrome 57 Canary with chrome://flags/#enable-service-worker-navigation-preload enabled.

Activating navigation preload

addEventListener('activate', event => {
  event.waitUntil(async function() {
    // Feature-detect
    if (self.registration.navigationPreload) {
      // Enable navigation preloads!
      await self.registration.navigationPreload.enable();
    }
  }());
});

You can call navigationPreload.enable() whenever you want, or disable it with navigationPreload.disable(). However, since your fetch event needs to make use of it, it's best to enable/disable it in your service worker's activate event.

Using the preloaded response

Now the browser will be performing preloads for navigations, but you still need to use the response:

addEventListener('fetch', event => {
  event.respondWith(async function() {
    // Respond from the cache if we can
    const cachedResponse = await caches.match(event.request);
    if (cachedResponse) return cachedResponse;

    // Else, use the preloaded response, if it's there
    const response = await event.preloadResponse;
    if (response) return response;

    // Else try the network.
    return fetch(event.request);
  }());
});

event.preloadResponse is a promise that resolves with a response, if:

  • Navigation preload is enabled.
  • The request is a GET request.
  • The request is a navigation request (which browsers generate when they're loading pages, including iframes).

Otherwise event.preloadResponse is still there, but it resolves with undefined.

Warning: Don't enable navigation preload then forget to use it. If you use fetch(event.request) instead of event.preloadResponse, you'll end up with double requests for navigations.

Custom responses for preloads

If your page needs data from the network, the quickest way is to request it in the service worker and create a single streamed response containing parts from the cache and parts from the network.

Say we wanted to display an article:

addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  const includeURL = new URL(url);
  includeURL.pathname += 'include';

  if (isArticleURL(url)) {
    event.respondWith(async function() {
      // We're going to build a single request from multiple parts.
      const parts = [
        // The top of the page.
        caches.match('/article-top.include'),
        // The primary content
        fetch(includeURL)
          // A fallback if the network fails.
          .catch(() => caches.match('/article-offline.include')),
        // The bottom of the page
        caches.match('/article-bottom.include')
      ];

      // Merge them all together.
      const {done, response} = await mergeResponses(parts);
      // Wait until the stream is complete.
      event.waitUntil(done);
      // Return the merged response.
      return response;
    }());
  }
});

In the above, mergeResponses is a little function that merges the streams of each request. This means we can display the cached header while the network content streams in.

This is quicker than the "app shell" model as the network request is made along with the page request, and the content can stream without major hacks.

However, the request for includeURL will be delayed by the service worker's startup time. We can use navigation preload to fix this too, but in this case we don't want to preload the full page, we want to preload an include.

To support this, a header is sent with every preload request:

Service-Worker-Navigation-Preload: true

The server can use this to send different content for navigation preload requests than it would for a regular navigation request. Just remember to add a Vary: Service-Worker-Navigation-Preload header, so caches know that your responses differ.

Now we can use the preload request:

// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
  // Else do a normal fetch
  .then(r => r || fetch(includeURL))
  // A fallback if the network fails.
  .catch(() => caches.match('/article-offline.include'));

const parts = [
  caches.match('/article-top.include'),
  networkContent,
  caches.match('/article-bottom')
];

Note: Promise.resolve(event.preloadResponse) means we get a promise for undefined if event.preloadResponse is undefined. It's a good way to normalise behaviour with browsers that don't support event.preloadResponse.

Changing the header

By default, the value of the Service-Worker-Navigation-Preload header is true, but you can set it to whatever you want:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
  console.log('Done!');
});

You could, for example, set it to the ID of the last post you have cached locally, so the server only returns newer data.

Getting the state

You can look up the state of navigation preload using getState:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.getState();
}).then(state => {
  console.log(state.enabled); // boolean
  console.log(state.headerValue); // string
});

Use it on live sites today!

We're still experimenting with this feature, but we're looking for real-world feedback. To get feedback, we're making it available as an origin trial starting in Chrome 57. Origin trials allow you to temporarily enable the feature for users of your website, so you can test its real-world impact. To do this, you'll need to request a token for your origin, and include the token as a header on your pages and service worker:

Origin-Trial: token_obtained_from_signup

To avoid the problems we saw with vendor prefixes, origin trials are globally shut off if usage exceeds 0.03% of all Chrome page loads, so large sites should only enable the feature for a fraction of its users.

If you do experiment with this feature, you can send us feedback on our mailing list, or join the spec discussion. If you find any bugs, please file an issue, including the words "service worker navigation preload" in the issue Summary.

Many thanks to Matt Falkenhagen and Tsuyoshi Horo for their work on this feature, and help with this article. And a huge thanks to everyone involved in the standardisation effort

New in Chrome 57

$
0
0

New in Chrome 57

  • Chrome 57 adds support for display: grid - the new CSS Grid Layout specification.
  • You can now customize and respond to user input on the lock screen and notifications using the new Media Session API.
  • And there are plenty more!

I’m Pete LePage, let’s dive in and see what’s new for developers in Chrome 57!

CSS grid layout

Flexbox is a powerful layout tool. It makes many complex layouts possible, but it can only do layout in one dimension. Chrome 57 adds support for display: grid

  • the new CSS Grid Layout specification, adding a powerful new tool for creating two-dimensional grid-based layout systems, optimized for responsive user interface design.

Elements within the grid can span multiple columns or rows. Regions in a CSS grid can also be named, making layout code easier to understand.

Surma has an Updates post about it, and when you’re ready to dive in deeper, check out Rachel Andrew’s site: GridByExample.com.

Media Session API

One of the missing features from web media applications has been the ability to deeply integrate with the core media experience on mobile devices. In Chrome for Android, you can now customize the lock screen and notifications with media content using the new Media Session API.

By providing metadata to the browser about the content being played, you can create rich lock screen messaging that includes information such as title, artist, album name, and artwork. You can also listen for and respond to user actions taken on the notification itself, such as seeking or skipping.

If you want to get started quickly, Francois has a great Updates post, or check out the official Chrome media sessions samples repo on GitHub.

And more!

And there’s lots more!

  • There are some improvements to the Payment Request API.
  • You can specify the color of the text input cursor with the caret-color property.
  • Visual effects such as line color and style can be specified with new text-decoration properties.
  • The Fetch API Response class now supports the .redirected attribute to help avoid untrustworthy responses and reduce the risk of open redirectors.
  • All -webkit- prefixed IndexedDB global aliases have been removed, after their deprecation in M38.
  • And one of my favorites — new padStart and padEnd formatting methods that simplify string padding when aligning console output or printing numbers with a fixed number of digits.

These are just a few of the changes in Chrome 57 for developers.

Shout out to Igalia

Finally, a big shout out to the engineers and team at Igalia for their awesome work on Blink. They were instrumental on landing the new CSS Grid and caret-color features.

Stay up to date

If you want to stay up to date with Chrome and know what’s coming, be sure to subscribe to our channel, or follow @ChromiumDev on Twitter. And be sure to check out the videos from the Chrome Dev Summit for a deeper dive into some of the awesome things the Chrome team is working on.

I’m Pete LePage, and as soon as Chrome 58 is released, I’ll be right here to tell you -- what’s new in Chrome!

Subscribe to Chrome Developers on YouTube

Subscribe to our YouTube channel or our RSS feed

What's New In DevTools (Chrome 58)

$
0
0

What's New In DevTools (Chrome 58)

Welcome to the first installment of the DevTools release notes! From here on out, the first time you open a new version of Chrome, DevTools opens the What's New drawer with a link to the release notes for that version.

Highlights

  • The Timeline panel has been renamed to the Performance panel.
  • The Profiles panel has been renamed to the Memory panel.
  • Cookie values are now editable.
  • DevTools now automatically pauses before out-of-memory errors.

New features

Editable cookies

Double-click on a cell in the Cookies tab to edit that value.

Figure 1. Editing a cookie

Thanks to kdzwinel for the contribution!

Inspectable and editable CSS variables in the Styles pane

You can now inspect and edit CSS variables in the Styles pane. See CSS Variables Demo to try it out yourself.

Out-of-memory breakpoints

When an app allocates a lot of memory in a short amount of time, DevTools now automatically pauses and increases the heap limit. This enables you to inspect the heap, execute commands on the Console to free up memory, and continue debugging the issue. See One Small Step For Chrome, One Giant Heap For V8 for more information.

Figure 2. Paused on an out-of-memory breakpoint

Breakpoints on canvas creation

You can now create event listener breakpoints that are triggered whenever a new canvas context is created.

Figure 3. Canvas creation breakpoints via the Create canvas context checkbox in the Event Listener Breakpoints pane

Start time stats in the Timing tab

At the top of the Timing tab, you can now see when a request was queued and started.

Figure 4. Start time stats in the Timing tab

Server stats in the Timing tab

You can now insert custom server statistics into the Timing tab. See Demo of server timing values for an example.

Figure 5. Server stats in the Timing tab

Changes

The Timeline panel is now the Performance panel

The Timeline panel has been renamed to the Performance panel, to better reflect its purpose.

The Profiles panel is now the Memory panel

The Profiles panel has been renamed to the Memory panel, to better reflect its purpose.

The CPU Profiler is behind a hidden panel

Now that the Profiles panel is called the Memory panel, it doesn't really make sense to have the CPU profiler on that panel anymore. Furthermore, the long-term goal is to get all users profiling from the Performance panel. In the meantime, you can still access the old CPU profiler from Settings > More Tools > JavaScript Profiler.

See Chrome DevTools: JavaScript CPU Profiling in Chrome 58 to learn how to profile CPU in the Performance panel.

New Console UI

The Console panel and drawer have undergone some UI changes. Some unpopular features have been moved to more hidden locations, and popular features are now more easily accessible.

  • Click Console Settings Console Settings to access settings for customizing the Console's behavior.
  • Preserve log is now hidden in Console Settings.
  • The Filters button and pane is gone. Use the dropdown menu instead.
  • The text box for filtering logs is now always shown. It was previously hidden in the Filters pane.
  • The filtering text box automatically accepts RegEx, so the Regex checkbox is gone.
  • The Hide violations checkbox is gone. Set the logging level dropdown to Verbose to see violations.
  • Unchecking the Show all messages checkbox in the old UI is equivalent to checking the Selected context only checkbox in Console Settings in the new UI.
Figure 6. The new Console UI

WebGL event listener breakpoints have moved

The WebGL event listener breakpoints have moved from the WebGL category to the Canvas category. The WebGL category has been removed.

Background Tabs in Chrome 57

$
0
0

Background Tabs in Chrome 57

Background tabs can have a dramatic negative effect on browser performance, especially on battery life. To mitigate this, Chrome has been placing various restrictions on background tabs for the last several years. Recently there’s been a number of efforts to make further improvements, and this document gives an overview of the Chrome policy. This document focuses on describing current policies in Chrome 57. Long-term strategy and further plans can be found in this document.

Optimising an application for background

Web developers should be aware that users often have a lot of tabs open in the background and it can have a serious effect on power usage and battery life. Work in the background should be kept to a minimum unless it’s absolutely necessary to provide a particular user experience. The Page visibility API should be used to detect when page is the backgrounded and suspend all unnecessary work like visual updates.

For some sites, this simple optimisation can reduce CPU usage by as much as 75%:

var doVisualUpdates = true;

document.addEventListener('visibilitychange', function(){
  doVisualUpdates = !document.hidden;
});

function update() {
  if (!doVisualUpdates) {
    return;
  }
  doStuff();
}

Policies

requestAnimationFrame()

Per the documentation, Chrome does not call requestAnimationFrame() when a page is in the background. This behavior has been in place since 2011.

Background timer alignment

Since Chrome 11, each independent timer is run no more than once per second. Chrome runs these timers in batches once a second, ensuring that the number of process wakeups is kept to a minimum. Pages playing audible audio are considered user-visible and exempted from background timer throttling. Exemption lasts for several seconds after audio stops playing to allow applications to queue the next audio track.

Note that audio is considered audible when and only when Chrome shows the audio icon. Silent audio streams do not grant exemptions.

Budget-based background timer throttling

Shipping in Chrome 57, budget-based timer throttling is a further extension of the timer alignment mechanism, placing an additional limit on background timer CPU usage. It operates as follows:

  • Each background tab has a time budget (in seconds) for running timers in the background.
  • A page is subjected to time budget limitations after 10 seconds in the background.
  • A timer task is allowed to run only when the time budget is non-negative.
  • After a timer has executed, its run time is subtracted from the budget.
  • The budget continuously regenerates with time (currently set to a rate of 0.01 seconds per second). Note that this budget regeneration rate can be tweaked as Chrome collects more data about throttling behavior.

There are a number of automatic exemptions from this throttling:

  • Applications playing audio are considered foreground and aren’t throttled.
  • Applications with real-time connections (WebSockets and WebRTC), to avoid closing these connections by timeout. The run-timers-once-a-second rule is still applied in these cases.

Note that this his mechanism uses wall time, not CPU time. It’s a good approximation of CPU time and penalises blocking the main thread for a long time.

Finally, remember that if you are using long tasks in the background, your application can be throttled for a very long period of time (up to 100 times the duration of your task). Split your work in to chunks of 50ms or less per the performance guidelines and use the visibilityChange listener to avoid doing unnecessary work in background.

Opt-outs

Chrome provides the --disable-background-timer-throttling flag for use cases like running test suites and other user-sanctioned heavy computations.

Deprecations and Removals in Chrome 58

$
0
0

Deprecations and Removals in Chrome 58

In nearly every version of Chrome, we see a significant number of updates and improvements to the product, its performance, and also capabilities of the Web Platform. This article describes the deprecations and removals in Chrome 58, which is in beta as of March 16. This list is subject to change at any time.

Mouse on Android stops firing TouchEvents

Until Chrome 57, Android low-level mouse events in Chrome primarily followed an event path designed for touch interactions. For example, mouse drag motion while a mouse button is pressed generates MotionEvents delivered through View.onTouchEvent.

But since touch events cannot support hover, hovering mousemoves followed a separate path. The design had many side-effects including mouse interactions firing TouchEvents, all mouse buttons appearing as left mouse buttons, and MouseEvents being suppressed by TouchEvents.

Starting with Chrome 58, a mouse on Android M or later will:

  • No longer fire TouchEvents.
  • Fire a consistent sequence of MouseEvents with appropriate buttons and other properties.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Remove case-insensitive matching for usemap attribute

The usemap attribute was formerly defined as caseless. Unfortunately implementing this was complicated enough that no browsers implemented it correctly. Research suggested that such a complicated algorithm is unnecessary, and even ASCII case-insensitive matching is unnecessary.

Consequently, the specification was updated so that case-sensitive matching is applied. The old behavior was deprecated in Chrome 57, and is now removed.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Remove EME from non-secure contexts

Some usages of Encrypted Media Extenions (EME) expose digital rights management implementations that are not open source, involve access to persistent unique identifiers, and/or run unsandboxed or with privileged access. Security risks are increased for sites exposed via non-secure HTTP because they can be attacked by anyone on the channel. Additionally, when user consent is required, acceptance persisted for a non-secure HTTP site can be exploited by such an attacker.

Support for non-secure contexts was removed from the EME version 1 spec and is not supported in the proposed recommendation nor anticipated in the subsequent final. will not be in the upcoming proposed recommendation or subsequent final recommendation. The API has been showing a deprecation message on non-secure origins since Chrome 44 (May 2015). In Chrome 58, it is now removed. This change is part of our broader effort to remove powerful features from unsecure origins.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Remove legacy caller for HTMLEmbedElement and HTMLObjectElement

That an interface has a legacy caller means that an instance can be called as a function. Currently, HTMLEmbedElement and HTMLObjectElement support this functionality. In Chrome 57 this ability was deprecated. Starting in Chrome 58, calling throws an exception.

This change brings Chrome in line with recent spec changes. The legacy behavior is not supported in Edge or Safari, and it is being removed from Firefox.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Remove deprecated names for motion path properties

Motion path CSS properties allow authors to animate any graphical object along an author-specified path. In compliance with the spec, several properties were implemented in Chrome 45. The names of these properties were changed in the spec in mid 2016. Chrome implemented the new names in Chrome 55 and Chrome 56. Console deprecation warnings were also implemented.

In Chrome 58, the old property names are being removed. The affected properties and their new names are shown below.

Removed Property Current Name
motion-path offset-path
motion-offset offset-distance
motion-rotation offset-rotate
motion offset

Intent to Remove

Remove support for commonName matching in certificates

RFC 2818 describes two methods to match a domain name against a certificate: using the available names within the subjectAlternativeName extension, or, in the absence of a SAN extension, falling back to the commonName. The fallback to the commonName was deprecated in RFC 2818 (published in 2000), but support remains in a number of TLS clients, often incorrectly.

The use of the subjectAlternativeName fields leaves it unambiguous whether a certificate is expressing a binding to an IP address or a domain name, and is fully defined in terms of its interaction with Name Constraints. However, the commonName is ambiguous, and because of this, support for it has been a source of security bugs in Chrome, the libraries it uses, and within the TLS ecosystem at large.

The compatibility risk for removing commonName is low. RFC 2818 has deprecated this for nearly two decades, and the baseline requirements (which all publicly trusted certificate authorities must abide by) has required the presence of a subjectAltName since 2012. Firefox already requires the subjectAltName for any newly issued publicly trusted certificates since Firefox 48.

Note: Enterprises that need to support such certificates for internal purposes may set the EnableCommonNameFallbackForLocalAnchors Enterprise policy.

Intent to Remove | Chromestatus Tracker | Chromium Bug

Remove webkitdropzone global attribute

The dropzone global attribute was introduced by the HTML5 drag and drop specification as a declarative method for specifying an HTML element's willingness to be the target of a drag-and-drop operation, the content types that can be dropped onto the element, and the drag-and-drop operation (copy/move/link).

The attribute failed to gain traction among browser vendors. Blink and WebKit only implement a prefixed form of the attribute, webkitdropzone. Because the dropzone attribute was removed from the spec in early March 2017 the prefixed version is being removed from Chrome.

Intent to Remove | Chromestatus Tracker | Chromium Bug

The interface elements regions, addRegion() and removeRegion(), have been removed from the WebVTT spec and is removed in Chrome 58 to comply with the latest spec. We expect little impact from this removal since the feature was never enabled by default (meaning it was behind a flag). Those needing an alternative can use the VTTCue.region property which is being added in Chrome 58.

Chromestatus Tracker | Chromium Bug

WebAudio: remove AudioSourceNode interface

The AudioSourceNode interface is not part of the Web Audio specification, is not constructible, and has no attributes so it basically has no developer- accessible functionality. Therefore it is being removed.

Intent to Remove | Chromestatus Tracker | Chromium Bug

<<../../_deprecation-policy.md>>

We'll Cross the (Media) Streams, Ray

$
0
0

We'll Cross the (Media) Streams, Ray

Today, when using Media Source Extensions (MSE) in Chrome, it's not possible to switch between encrypted and clear streams. This is actually not prohibited by the MSE spec. Rather, this limitation is mostly in how the media pipeline is setup to support Encrypted Media Extensions (EME).

MSE requires that media streams start with an initialization segment which includes information like codec initialization data, and encryption information. Typically, the initialization segment is at the beginning of a media file. Consequently, when media are attached to a media element via download or MSE, they "just work".

The problem comes when you try to change media characteristics in mid stream. Changing media characteristics requires passing a new initialization segment. For most characteristics, this works. Playback continues. The exception is the encryption settings. The encryption settings from the first initialization segment only signal whether the stream segments may be encrypted, meaning clear media segments can be inserted in the stream. Corolary to this is that an unencrypted stream with even a single encrypted segment requires that encryption information be included in the initialization segment. Because of this, ad insertion requires workarounds that don't apply to other platforms.

Starting in Chrome 58, all this changes. You can now switch between encrypted and unencrypted in the same stream. This improves compatibility by matching behavior that already exists in Firefox and Edge.

This has a few caveats. First, if you anticipate any encrypted segments in your media streams, you must set the MediaKeys up front. As before, you cannot mix HTTP and HTTPS in the same source.


Media Updates in Chrome 58

$
0
0

Media Updates in Chrome 58

Media controls customization

Developers can now customize Chrome's native media controls such as the download, fullscreen and remoteplayback buttons using the new ControlsList API.

Native media controls in Chrome 58
Figure 1. Native media controls in Chrome 58

This API offers a way to show or hide native media controls that do not make sense or are not part of the expected user experience, or only whitelist a limited set of features.

The current implementation for now is a blacklist mechanism on native controls with the ability to set them directly from HTML content using the new attribute controlsList. Check out the official sample.

Usage in HTML:

<video controls controlsList="nofullscreen nodownload noremote foobar"></video>

Usage in Javascript:

var video = document.querySelector('video');
video.controls; // true
video.controlsList; // "nofullscreen nodownload noremote" - "foobar" not present
video.controlsList.remove('noremote');
video.controlsList; // "nofullscreen nodownload" - "noremote" not present
video.getAttribute('controlsList'); // "nofullscreen nodownload"

Intent to Ship | Chromestatus Tracker | Chromium Bug

Autoplay for Progressive Web Apps added to home screen

Previously, Chrome used to block all autoplay with sound on Android without exception. This is no longer true. From now on, sites installed using the improved Add to Home Screen flow are allowed to autoplay audio and video served from origins included in the web app manifest's scope without restrictions.

{
  "name": "My Web App",
  "description": "An awesome app",
  "scope": "/foo",
  ...
}
<!-- Audio will autoplay as /foo is in the scope. -->
<audio autoplay src="/foo/file.mp4"></audio>

<!-- Audio fails to autoplay as /bar is NOT in the scope. -->
<audio autoplay src="/bar/file.mp4"></audio>

Intent to Ship | Chromestatus Tracker | Chromium Bug

Pause autoplaying muted video when invisible

As you may already know, Chrome on Android allows muted videos to begin playing without user interaction. If a video is marked as muted and has the autoplay attribute, Chrome starts playing the video when it becomes visible to the user.

From Chrome 58, in order to reduce power usage, playback of videos with the autoplay attribute will be paused when offscreen and resumed when back in view, following Safari iOS behavior.'

Note: This only applies to videos that are declared as autoplay but not videos that start playing with play().

Intent to Ship | Chromestatus Tracker | Chromium Bug

color-gamut media query

As wide color gamut screens are more and more popular, sites can now access the approximate range of colors supported by Chrome and output devices using the color-gamut media query.

If you're not familiar yet with the definitions of color space, color profile, gamut, wide-gamut and color depth, I highly recommend you read the Improving Color on the Web WebKit blog post. It goes into much detail on how to use the color-gamut media query to serve wide-gamut images when the user is on wide-gamut displays and fallback to sRGB images otherwise.

The current implementation in Chrome accepts the srgb, p3 (gamut specified by the DCI P3 Color Space), and rec2020 (gamut specified by the ITU-R Recommendation BT.2020 Color Space) keywords. Check out the official sample.

Usage in HTML:

<picture>
  <source media="(color-gamut: p3)" srcset="photo-p3.jpg">
  <source media="(color-gamut: rec2020)" srcset="photo-rec2020.jpg">
  <img src="photo-srgb.jpg">
</picture>

Usage in CSS:

main {
  background-image: url("photo-srgb.jpg");
}

@media (color-gamut: p3) {
  main {
    background-image: url("photo-p3.jpg");
  }
}

@media (color-gamut: rec2020) {
  main {
    background-image: url("photo-rec2020.jpg");
  }
}

Usage in JavaScript:

// It is expected that the majority of color displays will return true.
if (window.matchMedia("(color-gamut: srgb)").matches) {
  document.querySelector('main').style.backgroundImage = 'url("photo-srgb.jpg")';
}

if (window.matchMedia("(color-gamut: p3)").matches) {
  document.querySelector('main').style.backgroundImage = 'url("photo-p3.jpg")';
}

if (window.matchMedia("(color-gamut: rec2020)").matches) {
  document.querySelector('main').style.backgroundImage = 'url("photo-rec2020.jpg")';
}

For info, this screen currently supports approximately:

  • the sRGB gamut or more.
  • the gamut specified by the DCI P3 Color Space or more.
  • the gamut specified by the ITU-R Recommendation BT.2020 Color Space or more.

Intent to Ship | Chromestatus Tracker | Chromium Bug

Building performant expand & collapse animations

$
0
0

Building performant expand & collapse animations

TL;DR

Use scale transforms when animating clips. You can prevent the children from being stretched and skewed during the animation by counter-scaling them.

Previously we’ve posted updates on how to create performant parallax effects and infinite scrollers. In this post we’re going to look over what’s involved if you want performant clip animations. If you want to see a demo, check out the Sample UI Elements GitHub repo.

Take, for example, an expanding menu:

Some options for building this are more performant than others.

Bad: Animating width and height on a container element

You could imagine using a bit of CSS to animate the width and height on the container element.

.menu {
  overflow: hidden;
  width: 350px;
  height: 600px;
  transition: width 600ms ease-out, height 600ms ease-out;
}

.menu--collapsed {
  width: 200px;
  height: 60px;
}

The immediate problem with this approach is that it requires animating width and height. These properties require calculating layout and paint the results on every frame of the animation, which can be very expensive, and will typically cause you to miss out on 60fps. If that’s news to you then read our Rendering Performance guides, where you can get more information on how the rendering process works.

Bad: Use the CSS clip or clip-path properties.

An alternative to animating width and height might be to use the (now-deprecated) clip property to animate the expand and collapse effect. Or, if you prefer, you could use clip-path instead. Using clip-path, however, is less well supported than clip. But clip is deprecated. Right. But don’t despair, this isn’t the solution you wanted anyway!

.menu {
  position: absolute;
  clip: rect(0px 112px 175px 0px);
  transition: clip 600ms ease-out;
}

.menu--collapsed {
  clip: rect(0px 70px 34px 0px);
}

While better than animating the width and height of the menu element, the downside of this approach is that it still triggers paint. Also the clip property, should you go that route, requires that the element it’s operating on is either absolutely or fixed positioned, which can require a little extra wrangling.

Good: animating scales

Since this effect involves something getting bigger and smaller, you can use a scale transform. This is great news because changing transforms is something that doesn’t require layout or paint, and which the browser can hand off to the GPU, meaning that the effect is accelerated and significantly more likely to hit 60fps.

The downside to this approach, like most things in rendering performance, is that it requires a bit of setting up. It’s totally worth it, though!

Step 1: Calculate the start and end states

With an approach that uses scale animations, the first step is to read elements that tell you the size the menu needs to be both when it’s collapsed, and when it’s expanded. It may be that for some situations you can’t get both of these bits of information in one go, and that you need to — say — toggle some classes around to be able to read the various states of the component. If you need to do that, however, be cautious: getBoundingClientRect() (or offsetWidth and offsetHeight) forces the browser to run styles and layout passes if styles have changed since they were last run.

function calculateCollapsedScale () {
  // The menu title can act as the marker for the collapsed state.
  const collapsed = menuTitle.getBoundingClientRect();

  // Whereas the menu as a whole (title plus items) can act as
  // a proxy for the expanded state.
  const expanded = menu.getBoundingClientRect();
  return {
    x: collapsed.width / expanded.width,
    y: collapsed.height / expanded.height
  }
}

In the case of something like a menu, we can make the reasonable assumption that it will start out being its natural scale (1, 1). This natural scale represents its expanded state meaning you will need to animate from a scaled down version (which was calculated above) back up to that natural scale.

But wait! Surely this would scale the contents of the menu as well, wouldn’t it? Well, as you can see below, yes.

So what can you do about this? Well you can apply a counter-transform to the contents, so for example if the container is scaled down to 1/5th of its normal size, you can scale the contents up by 5x to prevent the contents being squashed. There are two things to notice about that:

  1. The counter-transform is also a scale operation. This is good because it can also be accelerated, just like the animation on the container. You may need to ensure that the elements being animated get their own compositor layer (enabling the GPU to help out), and for that you can add will-change: transform to the element or, if you need to support older browsers, backface-visiblity: hidden.

  2. The counter-transform must be calculated per frame. This is where things can get a little trickier, because assuming that the animation is in CSS and uses an easing function, the easing itself need to be countered when animating the counter-transform. However, calculating the inverse curve for — say — cubic-bezier(0, 0, 0.3, 1) isn’t all that obvious.

It may be tempting, then, to consider animating the effect using JavaScript. After all, you could then use an easing equation to calculate the scale and counter-scale values per frame. The downside of any JavaScript-based animation is what happens when the main thread (where your JavaScript runs) is busy with some other task. The short answer is that your animation can stutter or halt altogether, which isn’t great for UX.

Step 2: Build CSS Animations on the fly

The solution, which may appear odd at first, is to create a keyframed animation with our own easing function dynamically and inject it into the page for use by the menu. (Big thanks to Chrome engineer Robert Flack for pointing this out!) The primary benefit of this is that a keyframed animation that mutates transforms can be run on the compositor, meaning that it isn’t affected by tasks on the main thread.

To make the keyframe animation we step from 0 to 100 and calculate what scale values would be needed for the element and its contents. These can then be boiled down to a string, which can be injected into the page as a style element. Injecting the styles will cause a Recalculate Styles pass on the page, which is additional work that the browser has to do, but it will do it only once when the component is booting up.

function createKeyframeAnimation () {
  // Figure out the size of the element when collapsed.
  let {x, y} = calculateCollapsedScale();
  let animation = '';
  let inverseAnimation = '';

  for (let step = 0; step <= 100; step++) {
    // Remap the step value to an eased one.
    let easedStep = ease(step / 100);

    // Calculate the scale of the element.
    const xScale = x + (1 - x) * easedStep;
    const yScale = y + (1 - y) * easedStep;

    animation += `${step}% {
      transform: scale(${xScale}, ${yScale});
    }`;

    // And now the inverse for the contents.
    const invXScale = 1 / xScale;
    const invYScale = 1 / yScale;
    inverseAnimation += `${step}% {
      transform: scale(${invXScale}, ${invYScale});
    }`;

  }

  return `
  @keyframes menuAnimation {
    ${animation}
  }

  @keyframes menuContentsAnimation {
    ${inverseAnimation}
  }`;
}

The endlessly curious may be wondering about the ease() function inside the for-loop. You can use something like this to map values from 0 to 1 to an eased equivalent.

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

You can use Google search to plot what that looks like as well. Handy! If you’re in need of other easing equations do check out Tween.js by Soledad Penadés, which contains a whole heap of them.

Step 3: Enable the CSS Animations

With these animations created and baked out to the page in JavaScript, the final step is to toggle classes enabling the animations.

.menu--expanded {
  animation-name: menuAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

.menu__contents--expanded {
  animation-name: menuContentsAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

This causes the animations to run that were created in the previous step. Because the baked animations are already eased, the timing function needs to be set to linear otherwise you’ll ease between each keyframe which will look very weird!

When it comes to collapsing the element back down there are two options: update the CSS animation to run in reverse rather than forwards. This will work just fine, but the "feel" of the animation will be reversed, so if you used an ease-out curve the reverse will feel eased in, which will make it feel sluggish. A more appropriate solution is to create a second pair of animations for collapsing the element. These can be created in exactly the same way as the expand keyframe animations, but with swapped start and end values.

const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;

A more advanced version: circular reveals

It’s also possible to use this technique to make circular expand and collapse animations.

The principles are largely the same as the previous version, where you scale an element, and counter-scale its immediate children. In this case the element that’s scaling up has a border-radius of 50%, making it circular, and is wrapped by another element that has overflow: hidden, meaning that you don’t see the circle expand outside of the element bounds.

A word of warning on this particular variant: Chrome has blurry text on low DPI screens during the animation because of rounding errors due to the scale and counter-scale of the text. If you’re interested in the details for that there’s a bug filed that you can star and follow.

The code for the circular expand effect can be found in the GitHub repo.

Conclusions

So there you have it, a way to do performant clip animations using scale transforms. In a perfect world it would be great to see clip animations be accelerated (there’s a Chromium bug for that made by Jake Archibald), but until we get there you should be cautious when animating clip or clip-path, and definitely avoid animating width or height.

It would also be handy to use Web Animations for effects like this, because they have a JavaScript API but can run on the compositor thread if you only animate transform and opacity. Unfortunately support for Web Animations isn’t great, though you could use progressive enhancement to use them if they’re available.

if ('animate' in HTMLElement.prototype) {
  // Animate with Web Animations.
} else {
  // Fall back to generated CSS Animations or JS.
}

Until that changes, while you can use JavaScript-based libraries to do the animation, you might find that you get more reliable performance by baking a CSS animation and using that instead. Equally, if your app already relies on JavaScript for its animations you may be better served by being at least consistent with your existing codebase.

If you want to have a look through the code for this effect take a look at the UI Element Samples Github repo and, as always, let us know how you get on in the comments below.

CSS deep-dive: matrix3d() for a frame-perfect custom scrollbar

$
0
0

CSS deep-dive: matrix3d() for a frame-perfect custom scrollbar

Custom scrollbars are extremely rare and that’s mostly due to the fact that scrollbars are one of the remaining bits on the web that are pretty much unstylable (I’m looking at you, date picker). You can use JavaScript to build your own, but that’s expensive, low fidelity and can feel laggy. In this article we will leverage some unconventional CSS matrices to build a custom scroller that doesn’t require any JavaScript while scrolling, just some setup code.

TL;DR:

You don’t care about the nitty gritty? You just want to look at the Nyan cat demo and get the library? You can find the demo’s code in our GitHub repo.

LAM;WRA (Long and mathematical; will read anyways):

Note: This article does some weird stuff with homogeneous coordinates as well as matrix calculations. It is good to have some basic understanding of these concepts if you want to understand the intricate details of this trick. However, we hope that – even if you don’t enjoy matrix math – you can still follow along and see how we used them.

A while ago we built a parallax scroller (Did you read that article? It’s really good, well worth your time!). By pushing elements back using CSS 3D transforms, elements moved slower than our actual scrolling speed.

Recap

Let’s start off with a recap of how the parallax scroller worked.

As shown in the animation, we achieved the parallax effect by pushing elements “backwards” in 3D space, along the Z axis. Scrolling a document is effectively a translation along the Y axis. So if we scroll down by, say 100px, every element will be translated upwards by 100px. That applies to all elements, even the ones that are “further back”. But because they are farther away from the camera their observed on-screen movement will be less than 100px, yielding the desired parallax effect.

Of course, moving an element back in space will also make it appear smaller, which we correct by scaling the element back up. We figured out the exact math when we built the parallax scroller, so I won’t repeat all the details.

Step 0: What do we wanna do?

Scrollbars. That’s what we are going to build. But have you ever really thought about what they do? I certainly didn’t. Scrollbars are an indicator of how much of the available content is currently visible and how much progress you as the reader have made. If you scroll down, so does the scrollbar to indicate that you are making progress towards the end. If all the content fits into the viewport the scrollbar is usually hidden. If the content has 2x the height of the viewport, the scrollbar fills ½ of the height of the viewport. Content worth 3x the height of the viewport scales the scrollbar to ⅓ of the viewport etc. You see the pattern. Instead of scrolling you can also click-and-drag the scrollbar to move through the site faster. That’s a surprising amount of behavior for an inconspicuous element like that. Let’s fight one battle at a time.

Step 1: Putting it in reverse

Okay, we can make elements move slower than the scrolling speed with CSS 3D transforms as outlined in the parallax scrolling article. Can we also reverse the direction? It turns out we can and that’s our way in for building a frame-perfect, custom scrollbar. To understand how this works we need to cover a few CSS 3D basics first.

To get any kind of perspective projection in the mathematical sense, you’ll most likely end up using homogeneous coordinates. I’m not going into detail what they are and why they work, but you can think of them like 3D coordinates with an additional, fourth coordinate called w. This coordinate should be 1 except if you want to have perspective distortion. We don’t need to worry about the detials of w as we are not going to use any other value than 1. Therefore all points are from now on 4-dimensional vectors [x, y, z, w=1] and consequently matrices need to be 4x4 as well.

One occasion where you can see that CSS uses homogeneous coordinates under the hood is when you define your own 4x4 matrices in a transform property using the matrix3d() function. matrix3d takes 16 arguments (because the matrix is 4x4), specifying one column after the other. So we can use this function to manually specify rotations, translations etc.. But what it also allows us to do is mess around with that w coordinate!

Before we can make use of matrix3d(), we need a 3D context – because without a 3D context there wouldn’t be any perspective distortion and no need for homogeneous coordinates. To create a 3D context we need a container with a perspective and some elements inside that we can transform in the newly created 3D space. For example:

A piece of CSS code that distorts a div using CSS’
    perspective attribute.

The elements inside a perspective container are processed by the CSS engine as follows:

  • Turn each corner (vertex) of an element into homogenous coordinates [x,y,z,w], relative to the perspective container.
  • Apply all of the element’s transforms as matrices from right to left.
  • If the perspective element is scrollable, apply a scroll matrix.
  • Apply the perspective matrix.

The scroll matrix is a translation along the y axis. If we scroll down by 400px, all elements need to be moved up by 400px. The perspective matrix is a matrix that “pulls” points closer to the vanishing point the further back in 3D space they are. This achieves both effects of making things appear smaller when they are farther back and also makes them “move slower” when being translated. So if an element is pushed back, a translation of 400px will cause the element to only move 300px on the screen.

If you want to know all the details, you should read the spec on CSS’ transform rendering model, but for the sake of this article I simplified the algorithm above.

Our box is inside a perspective container with value p for the perspective attribute, and let’s assume the container is scrollable and is scrolled down by n pixels.

Perspective matrix times scroll matrix times element transform matrix
  equals four by four identity matrix with minus one over p in the fourth row
  third column times four by four identity matrix with minus n in the second
  row fourth column times element transform matrix.

The first matrix is the perspective matrix, the second matrix is the scroll matrix. To recap: The scroll matrix’ job is to make an element move up when we are scrolling down, hence the negative sign.

For our scrollbar however we want the opposite – we want our element to move down when we are scrolling down. Here’s where we can use a trick: Inverting the w coordinate of the corners of our box. If the w coordinate is -1, all translations will take effect in the opposite direction. So how do we do that? The CSS engine takes care of converting the corners of our box to homogeneous coordinates, and sets w to 1. It’s time for matrix3d() to shine!

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    );
}

This matrix will do nothing else then negating w. So when the CSS engine has turned each corner into a vector of the form [x,y,z,1], the matrix will convert it into [x,y,z,-1].

Four by four identity matrix with minus one over p in the fourth row
  third column times four by four identity matrix with minus n in the second
  row fourth column times four by four identity matrix with minus one in the
  fourth row fourth column times four dimensional vector x, y, z, 1 equals four
  by four identity matrix with minus one over p in the fourth row third column,
  minus n in the second row fourth column and minus one in the fourth row
  fourth column equals four dimensional vector x, y plus n, z, minus z over
  p minus 1.

I listed an intermediate step to show the effect of our element transform matrix. If you are not comfortable with matrix math, that is okay. The Eureka moment is that in the last line we end up adding the scroll offset n to our y coordinate instead of subtracting it. The element will be translated downwards if we scroll down.

However, if we just put this matrix in our example, the element will not be displayed. This is because the CSS spec requires that any vertex with w < 0 blocks the element from being rendered. And since our z coordinate is currently 0, and p is 1, w will be -1.

Luckily, we can choose the value of z! To make sure we end up with w=1, we need to set z = -2.

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    translateZ(-2px);
}

Lo and behold, our box is back!

Step 2: Make it move

Now our box is there and is looking the same way it would have without any transforms. Right now the perspective container is not scrollable, so we can’t see it, but we know that our element will go the other direction when scrolled. So let’s make the container scroll, shall we? We can just add a spacer element that takes up space:

<div class="container">
  <div class="box"></div>
  <span class="spacer"></span>
</div>

<style>
/* … all the styles from the previous example … */
.container {
  overflow: scroll;
}
.spacer {
  display: block;
  height: 500px;
}
</style>

And now scroll the box! The red box moves down.

Step 3: Give it a size

We have an element that moves down when the page scrolls down. That’s the hard bit out of the way, really. Now we need to style it to look like a scrollbar and make it a bit more interactive.

A scrollbar usually consists of a “thumb” and a “track”, while the track isn’t always visible. The thumb’s height is directly proportional to how much of the content is visible.

<script>
  const scroller = document.querySelector('.container');
  const thumb = document.querySelector('.box');
  const scrollerHeight = scroller.getBoundingClientRect().height;
  thumb.style.height = /* ??? */;
</script>

scrollerHeight is the height of the scrollable element, while scroller.scrollHeight is the total height of the scrollable content. scrollerHeight/scroller.scrollHeight is the fraction of the content that is visible. The ratio of vertical space the thumb covers should be equal to the ratio of content that is visible:

thumb dot style dot height over scrollerHeight equals scroller height
  over scroller dot scroll height if and only if thumb dot style dot height
  equals scroller height times scroller height over scroller dot scroll
  height.

<script>
  // …
  thumb.style.height =
    scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
  // Accommodate for native scrollbars
  thumb.style.right =
    (scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>

Note: We need to adjust right so our custom scrollbar is visible on systems with permanent native scrollbars. We will completely hide the native scrollbars later with a trick.

The size of the thumb is looking good, but it’s moving way too fast. This is where we can grab our technique from the parallax scroller. If we move the element further back it will move slower while scrolling. We can correct the size by scaling it up. But how much should we push it back exactly? Let’s do some – you guessed it – math! This is the last time, I promise.

The crucial bit of information is that we want the bottom edge of the thumb to line up with the bottom edge of the scrollable element when scrolled all the way down. In other words: If we have scrolled scroller.scrollHeight - scroller.height pixels, we want our thumb to be translated by scroller.height - thumb.height. For every pixel of scroller, we want our thumb to move a fraction of a pixel:

factor equals scroller dot height minus thumb dot height over scroller
  dot scroll height minus scroller dot height.

That’s our scaling factor. Now we need to convert the scaling factor into a translation along the z axis, which we already did in the parallax scrolling article. According to the relevant section in the spec: The scaling factor is equal to p/(p − z). We can solve this equation for z to figure out how much we we need to translate our thumb along the z axis. But keep in mind that due to our w coordinate shenanigans we need to translate an additional -2px along z. Also note that an element’s transforms are applied right to left, meaning that all translations before our special matrix will not be inverted, all translations after our special matrix, however, will! Let’s codify this!

<script>
  // ... code from above...
  const factor =
    (scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
  thumb.style.transform = `
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    scale(${1/factor})
    translateZ(${1 - 1/factor}px)
    translateZ(-2px)
  `;
</script>

We have a scrollbar! And it’s just a DOM element that we can style however we like. One thing that is important to do in terms of accessibility is to make the thumb respond to click-and-drag, as many users are used to interacting with a scrollbar that way. For the sake of not making this blog post even longer, I am not going explain the details for that part. Take a look at the library code for details if you want to see how it’s done.

What about iOS?

Ah, my old friend iOS Safari. As with the parallax scrolling, we run into an issue here. Because we are scrolling on an element, we need to specify -webkit-overflow-scrolling: touch, but that causes 3D flattening and our entire scrolling effect stops working. We solved this problem in the parallax scroller by detecting iOS Safari and relying on position: sticky as a workaround, and we’ll do exactly the same thing here. Take a look at the parallaxing article to refresh your memory.

What about the browser scrollbar?

On some systems we will have to deal with a permanent, native scrollbar. Historically, the scrollbar can’t be hidden (except with a non-standard pseudo-selector). So to hide it we have to resort to some (math-free) hackery. We wrap our scrolling element in a container with overflow-x: hidden and make the scrolling element wider than the container. The browser’s native scrollbar is now out of view.

Fin

Putting it all together, we can now build a frame-perfect custom scrollbar – like the one in our Nyan cat demo.

If you can’t see Nyan cat, you are experiencing a bug that we found and filed while building this demo (click the thumb to make Nyan cat appear). Chrome is really good at avoiding unnecessary work like painting or animating things that are off-screen. The bad news is that our matrix shenanigans make Chrome think the Nyan cat gif is actually off-screen. Hopefully this will get fixed soon.

There you have it. That was a lot of work. I applaud you for reading the entire thing. This is some real trickery to get this working and it’s probably rarely worth the effort, except when a customized scrollbar is an essential part of the experience. But good to know that it is possible, no? The fact that it is this hard to do a custom scrollbar shows that there’s work to be done on CSS’s side. But fear not! In the future, Houdini’s AnimationWorklet is going to make frame-perfect scroll-linked effects like this a lot easier.

Chromium policy on JavaScript dialogs

$
0
0

Chromium policy on JavaScript dialogs

History of JavaScript dialogs

JavaScript was introduced in 1995, and in the very first version of JavaScript were methods on the window object named alert(), confirm(), and prompt().

While they fit into the JavaScript of the time, their synchronous API is problematic for modern browsers. Because the JavaScript engine needs to pause until a user response is obtained, the JavaScript dialogs are app-modal. And because the dialogs are app-modal, they commonly (and unfortunately) are used to harm our users.

Because of this, the Chromium team highly recommends that you not use JavaScript dialogs.

Alternatives

There are many options for dialog replacement.

There are several choices for alert()/confirm()/prompt(). For notifying the user of events (e.g. calendaring sites), the Notifications API should be used. For obtaining user input, the HTML <dialog> element should be used. For XSS proofs-of-concept, devtool’s console.log(document.origin) can be used.

As for onbeforeunload, it should be noted that it is already unreliable. As Ilya Grigorik points out, “You cannot rely on pagehide, beforeunload, and unload events to fire on mobile platforms.” If you need to save state, you should use the Page Visibility API.

Changes

The ability for a page to specify the onbeforeunload string was removed in Chrome 51. (It was also removed by Safari starting with Safari 9.1 and in Firefox 4.)

alert()/confirm()/prompt() dialogs are being changed. Rather than being app-modal, they will be dismissed when their tab is switched from. (Safari 9.1 already does this.) This is fully enabled on the canary and dev channels and partially enabled on the beta and stable channels, and will be enabled more in the future.

The current plan for beforeunload dialogs is to require a user gesture to allow them to show. (This would not change the dispatching of the beforeunload event.) This aligns Chromium with Firefox, which made this change with Firefox 44.

Because of these changes, if your site uses dialogs, it is highly recommended that you move to using the earlier-mentioned alternatives so that this will not affect you.

What's New In DevTools (Chrome 59)

$
0
0

What's New In DevTools (Chrome 59)

Welcome to another installment of the DevTools release notes. Here's what's new for Chrome 59.

Note: You can check which version of Chrome you're running at chrome://help.

Highlights

New features

CSS and JS code coverage

Find unused CSS and JS code with the new Coverage tab. When you load or run a page, the tab tells you how much code was used, versus how much was loaded. You can reduce the size of your pages by only shipping the code that you need.

The Coverage tab
Figure 1. The Coverage tab

To open the tab:

  1. Open the Command Menu.
  2. Start typing Coverage and select Show Coverage.

Full-page screenshots

Check out the video below to learn how to take a screenshot from the top of the page, all the way to the bottom.

Block requests

Want to see how your page behaves when a particular script, stylesheet, or other resource isn't available? Right-click on the request in the Network panel and select Block Request URL. A new Request blocking tab pops up in the Drawer, which lets you manage blocked requests.

Block Request URL
Figure 2. Block Request URL

Step over async await

Up until now, trying to step through code like the snippet below was a headache. You'd be in the middle of test(), stepping over a line, and then you'd get interrupted by the setInterval() code. Now, when you step through async code like test(), DevTools steps from the first to last line with consistency.

function wait(ms) {
  return new Promise(r => setTimeout(r, ms)).then(() => "Yay");
}

// do some work in background.
setInterval(() => 42, 200);

async function test() {
  debugger;
  const hello = "world";
  const response = await fetch('index.html');
  const tmp = await wait(1000);
  console.log(tmp);
  return hello;
}

async function runTest() {
  let result = await test();
  console.log(result);
}

P.S. want to level up your debugging skills? Check out these new-ish docs:

Workspaces 2.0

The new UX for using DevTools as your code editor, also known as Workspaces or Persistence, has been released from Experiments.

  1. Go to the Sources panel.
  2. Click the Filesystem tab.
  3. Click Add folder to workspace.
  4. Select the folder in your filesystem containing your source code.
  5. Click Allow to grant DevTools read and write access to the folder.

DevTools automagically maps the files in your filesystem to the files being served from the network. No more need to manually map one to the other.

A network file mapped to the filesystem
Figure 3. The green dot next to (index) means that it's been mapped to a filesystem resource

Changes

Unified Command Menu

When you open the Command Menu now, notice that your command is prepended with a greater-than character (>). This is because the Command Menu has been unified with the Open File menu, which is Command+O (Mac), or Control+O (Windows, Linux).

Viewing all 599 articles
Browse latest View live