February 27, 2023
  • Perspectives
  • PWA
  • Safari

Safari… Catches Up?

Mike Hartington

Director of Developer Relation...

In case you missed the news, Apple shipped iOS/iPadOS 16.4 beta 1 the other day, which includes a ton of new features. Most notably, there are new features and improvements to WebKit/Safari. This includes improvements for Web Components, new Web APIs like the Wake Lock API, and the most notable… Web Push for iOS/iPadOS 🎉. Let’s take a closer look at these features and get a sense of what they mean for Safari and the greater web community

Web Push: Yay?

Anyone who knows me knows that I am not that big of a fan of push notifications. Any time an app asks for push notifications, I often reject them as I find them too noisy, but I’m well aware that many apps have a valid use case for them. From the WebKit announcement, the Web Push API is based on the W3C standard and uses the same services as native apps. This means that your notifications will be handled the same way as any app downloaded from the app store. It is worth noting that push notifications will only be allowed if the app is added to the user’s home screen.

Given Apple’s negative stance on providing a better add-to-home-screen experience, there will likely be some friction with this. Users will have to be taught how to add an app to the home screen from iOS, instead of getting a nicer prompt that is provided via Android/Chrome. All that being said, this is definitely a step in the right direction. To learn more about Web Push on iOS/Safari, check out the release blog and the Meet Web Push blog post from WebKit.

Better Web Components

As Web Components have evolved and become more commonplace, web developers and browser developers noticed places where they could be improved. Enter Declarative Shadow DOM and Form-Associated Custom Elements. These APIs take care of two big issues with Web Components: the need for JavaScript and components not being accessible. Let’s look at Declarative Shadow DOM first.

Declarative Shadow DOM introduces a new way to author the internals of a web component. For a refresher, Shadow DOM is an API that allows web component authors to encapsulate parts of the component and remove them from the main DOM tree. Anything rendered in the Shadow DOM gets its own shadow tree and is isolated from any CSS or JavaScript that an app developer might include.

However, Shadow DOM requires JavaScript and in many places cannot be parsed. This can lead to content not showing or, in places where Server Side Rendering (SSR) is being used, a flash of nothing before the JavaScript kicks in. Declarative Shadow DOM solves this by moving the content into the web component itself. For example, here’s the old way of using Shadow DOM:

class CompA extends HTMLElement {
  shadowRoot;
  constructor() {
    super();
    this.shadowRoot = this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';
  }
}
customElements.define('comp-a', CompA);
<comp-a></comp-a>

Now, this can be rewritten using Declarative Shadow DOM:

class CompB extends HTMLElement {
  constructor() {
    super();
  }
}
customElements.define('comp-b', CompB);
<comp-b>
  <template shadowrootmode="open">
    <h1>Hello Declarative Shadow DOM</h1>
  </template>
</comp-b>

While this can seem like a small change, by moving the content to the template, we can still render our content even if JavaScript is not enabled.


Forms Association solves another major problem: Web Components have a hard time working with forms. To solve this we can make use of ElementInternals which will allow us to set various attributes on the host element. From the WebKit blog, they show the example of:

class SomeButtonElement extends HTMLElement {
  #internals;
  #shadowRoot;
  constructor() {
    super();
    this.#internals = this.attachInternals();
    this.#internals.ariaRole = 'button';
    this.#shadowRoot = this.attachShadow({ mode: 'closed' });
    this.#shadowRoot.innerHTML = '<slot></slot>';
  }
}
customElements.define('some-button', SomeButtonElement);

Compared to an older example like so: 

class SomeButtonElement extends HTMLElement {
  #shadowRoot;
  constructor() {
    super();
    this.#shadowRoot = this.attachShadow({ mode: 'closed' });
    this.#shadowRoot.innerHTML = '<slot></slot>';
    this.setAttribute('role', 'button');
  }
}
customElements.define('some-button', SomeButtonElement);

While the two may look similar, the version using ElementInternals has a few benefits. 

1. Setting ariaRole via ElementInternals will allow for users to override the ariaRole where setAttribute will not.

2. No other built-in element calls setAttribute directly on itself.

There are more features provided by ElementInternals, but I’ll direct you to the WebKit blog to see more.

What this means for Ionic

We at Ionic see this as a welcomed improvement to Safari. In the codebase for the framework, there’s a lot of Safari specific code needed to account for things like Forms Association that will now be able to be removed. With Declarative Shadow DOM, the SSR story for Ionic apps becomes even better, too. And with Web Push, Progressive Web Apps… I mean, “home screen web apps” on iOS will start to feel like their Native counterparts (for better or for worse 🙃). All jokes aside, it’s great to see the WebKit team be able to ship these features and keep Safari competitive with other browsers out there.


Mike Hartington

Director of Developer Relation...