Standards – WebKit https://webkit.org Open Source Web Browser Engine Fri, 08 Mar 2024 23:18:51 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.2 An HTML Switch Control https://webkit.org/blog/15054/an-html-switch-control/ Wed, 28 Feb 2024 19:47:06 +0000 https://webkit.org/?p=15054 We’re very excited to introduce a new HTML form control as part of Safari 17.4: a switch.

Two switches as seen on iOS, with the second switch using a custom CSS accent-color.

The HTML for this example looks roughly like this:

<style> .special { accent-color: papayawhip } </style>
<input type=checkbox switch checked>
<input type=checkbox switch checked class=special>

Switches are a popular control on mobile platforms as well as in a large variety of UI frameworks, but until now they were not built into the web platform. Seeing the widespread need for this control we decided to change that.

The way we approached this is consistent with how other new form controls have been added to HTML:

  • We wanted it to have the look and feel of the OS control by default. It should match the end user’s preferences, and they should be able to manipulate the “thumb” of the switch in various ways.
  • We wanted to ensure it was backward compatible. A browser that does not support <input type=url> will treat it as <input type=text>. Likewise, a browser that does not support <input type=checkbox switch> will treat it as <input type=checkbox>.
  • We wanted the markup and API to be familiar. It mirrors that of checkboxes, except that the :indeterminate pseudo-class never matches.
  • We wanted the control to be accessible. Under the hood, it uses the ARIA switch role, announcing the states “On” and “Off” just like a switch from the OS. On macOS, if you enable “Differentiate Without Color”, or on iOS, if you enable “On/Off Labels”, the switch will show accessibility indicators.
  • We wanted the control to be stylable. As with <input type=checkbox>, if you use appearance: none, you get full control over its appearance as a web developer. It goes even a bit further as we ensured all properties will have their initial values when you use appearance: none. (See also the experimental ::thumb and ::track pseudo-elements explained below for planned enhancements in this area.) Control over styling is more limited with appearance: auto (the default). The accent-color property is supported and will be applied to the switch’s “track” background, as demonstrated in the image above.
  • We wanted the control to support a wide variety of languages, and as such, it has vertical rendering support out of the box. (This is in line with how we recently added vertical rendering support to the existing form controls.)

We’d love to see what you do with it and what else you would like this control to do.

Switch versus checkbox

Generally, we recommend using a switch when the end user understands the user interface element as a setting that is either “on” or “off”. A checkbox is well suited for when the end user would understand the element as something to be selected.

Experimental ::thumb and ::track pseudo-elements

In Safari if you go to Settings → Feature Flags, you can enable “::thumb and ::track pseudo-elements”, which gives web developers more control over the individual parts that make up a switch: its thumb and track. Essentially, this turns a single element into three, one parent with two sibling children.

We decided not to ship these pseudo-elements for now to give the standardization process additional time to finalize how they should work. We also want to make sure that when we ship them, they work for the other HTML controls they are designed for, such as <input type=range>. This way @supports(::thumb) will be all the feature detection you need.

Interactive demos of switch

Feedback appreciated

We’d love to hear what you make of this new HTML form control. Send a tweet to @webkit to share your thoughts on this feature. You can find us on Mastodon at @jensimmons@front-end.social and @jondavis@mastodon.social. You can also follow WebKit on LinkedIn. If you run into any issues, we welcome your WebKit bug reports on WebKit features like this. Reporting issues makes an enormous difference.

You can also download the latest Safari Technology Preview to try out new web platform features like this before they appear in a Safari beta.

And finally, a big thank you to Lily Spiniolas for doing a lot of work on this feature during her internship at Apple. Not just prototyping an implementation in WebKit, but also creating a pull request for the HTML Standard and carefully thinking through many aspects of its design.

]]>
Bringing Back Horizontal Rules in Select Elements https://webkit.org/blog/14933/bringing-back-horizontal-rules-in-select-elements/ Thu, 25 Jan 2024 18:18:14 +0000 https://webkit.org/?p=14933 In September 2023, Safari 17.0 on macOS shipped a small but interesting change to the <select> element. You can now put an <hr> element, known as a horizontal rule, inside a <select> element, which will draw a horizontal line again. Again, because Safari used to support this over a decade ago — more on that story later.

The horizontal rule creates visual breaks between options to help users scan and compare against similar options.

Depicts a select menu with Choose paper size label and the following options with visual separators between groups: 5.5 x 8.5 in, 8.5 × 11.0 in, 8.5 × 14.0 in, 11.0 x 17.0 in, A3, A4, A5, A6, Envelope #10, Envelope B5, Envelope C5, Envelope Monarch
Select element without separators; Select element with horizontal rule separators.

It’s a small change, but it’s been getting attention lately. Simply add an <hr> between <option> elements to insert a line:

<label for="papersize">Select Paper Size:</label>
<select name="papersize">
    <option>Select a paper size</option>
    <hr>
    <option>5.5 × 8.5 in</option>
    <option>8.5 × 11.0 in</option>
    <option>8.5 × 14.0 in</option>
    <option>11.0 × 17.0 in</option>
    <hr>
    <option>A3</option>
    <option>A4</option>
    <option>A5</option>
    <option>A6</option>
    <hr>
    <option>Envelope #10</option>
    <option>Envelope B5</option>
    <option>Envelope C5</option>
    <option>Envelope Monarch</option>
</select>

Interactive demo of Horizontal Rule elements in a Select element.

So why did this work for years but then stop? Where did it go?

An HTML parser regression story

Well over a decade ago, WebKit adopted a new HTML parser. It was based on the HTML5 standardization effort, which attempted to unify the HTML language, as it had diverged quite a bit in implementations. And it was a far cry from the SGML dialect some in the standardization community pretended HTML to be. It represented a huge milestone in the development of HTML. We were finally on a path where all implementations would agree on what any arbitrary byte stream of HTML represented.

Replacing WebKit’s HTML parser was a large undertaking. It brought huge benefits, but it was still missing a few bits when it shipped. In fact, one feature from WebKit had been hidden from HTML. You could still see it by manipulating the DOM or using XML, but apart from a couple of experts nobody knew.

That feature was hr elements nested in select elements. Originally it was added by Adele Peterson at Apple in 2006 to support a common UI paradigm on the web: separators between select box options. We discovered this while doing some maintenance work on the HTML parser and agreed this was still a desirable feature. We also re-discovered that in 2018 a feature request was opened against the HTML Standard for this exact feature.

To introduce this feature again at this stage required some careful changes to the HTML parser portion of the HTML Standard as well as some corresponding semantic and conformance changes. After all, we wanted to fix this regression while preserving HTML parser interoperability. At the same time we wanted to ensure the feature was properly standardized as well. With the help from others in the HTML standardization community we managed to make this change and it’s now part of multiple browsers and harder to accidentally regress again due to improved cross-browser test coverage.

That’s the story of how we lost access to separators in select boxes for a decade and then got them back, fully standardized, in Safari 17.0 (commit 263624).

If you’re still reading, you might wonder what other maintenance work we did on the HTML parser. It included a bunch of small fixes that made WebKit more standards compliant. Where it was appropriate, cross-browser test coverage was improved as well:

  • Handle comments directly that follow the body closing tag (commit 262222).
  • Correct a minor bug in CDATA handling (applies to SVG and MathML when used inside HTML) (commit 262408).
  • Fully remove support for command, layer, and nolayer elements (commits 262431 & 262553).
  • Corrected SVG and MathML attribute handling (commit 262502).
  • Added support for the search element (commit 264110).

Some notes about separators in select elements

It’s important to be aware that this feature adds visual separators. They are not announced by assistive technologies like VoiceOver.

It’s also worth noting that the HTML parser only supports <hr> as a child of <select> elements, not as a child of <optgroup> elements.

Lastly, when using a <select> element with a size attribute value greater than 1, the separators are instead rendered as blank space, similar to the space added for <optgroup> elements.

Feedback

Using <hr> in <select> gives authors another choice in how to visually separate options for users. Instead of the blank space rendered with <optgroup>, now authors can use lines too.

We love to hearing from you. Send a tweet to @webkit to share your thoughts on this feature. Find us on Mastodon at @jensimmons@front-end.social and @jondavis@mastodon.social. If you run into any issues, we welcome your WebKit bug reports on WebKit features like this. Reporting issues makes an enormouse difference.

You can also download the latest Safari Technology Preview to try out new web platform features like this before they appear in a Safari beta.

]]>
A quick introduction to the WPE WebKit Project https://webkit.org/blog/14149/a-quick-introduction-to-the-wpe-webkit-project/ Thu, 11 May 2023 16:00:47 +0000 https://webkit.org/?p=14149

As mentioned in a previous post here and also in the related post from the WPE WebKit blog, the WPE project is a port of WebKit which, at the time of this writing, is responsible for bringing WebKit to millions of embedded devices around the world: you can find it in set-top-boxes, cars, cooking machines, and smart home appliances, to name a few examples.

While there is already some information about the design and architecture of WPE on the wpewebkit.org website, the team behind WPE at Igalia has recently started a series of blog posts to explain different aspects of WPE in a more personal way by having different members of the team talk about specific areas they are most familiar with.

As a result, the first installment of the series is a high-level overview of the WPE project where Claudio Saavedra talks briefly about the architecture of the project and how it’s designed in terms of several modular components that enable integrators to quickly build performant, lightweight, and reliable Web browsers optimized for their specific devices in a flexible way.

Therefore, we would like to invite you to check out his blog post for a better idea of how the different pieces of the WPE WebKit project fit together and how the project is perfect to accomodate different needs and use cases. This is very important not only to the world of embedded devices, which benefits directly of this flexibility, but also to the whole WebKit ecosystem in general by providing a broader use base for our beloved Web engine.

Last but not least, feel free to check regularly the WPE WebKit blog for new installments of the series, where articles on different topics will be published in the future.

]]>
Badging for Home Screen Web Apps https://webkit.org/blog/14112/badging-for-home-screen-web-apps/ Tue, 25 Apr 2023 18:00:31 +0000 https://webkit.org/?p=14112 Along with the many other features for web apps on iOS and iPadOS 16.4, WebKit now includes support for the W3C’s Badging API.

A badged web application icon in the iOS dock showing the number 15.

This straightforward API allows web developers to badge the icon of a web app. This article explores the various features of the Badging API and how to use it, and our ongoing collaboration with W3C’s Web Applications Working Group to improve the specification.

Home Screen Web App Availability

In iOS and iPadOS 16.4, the Badging API is available exclusively for web apps the user has added to their home screen. You won’t find the API exposed to websites in Safari or other browsers, or in any app that uses WKWebView.

You can easily check for the availability of the API using feature detection for the setAppBadge function:

if ('setAppBadge' in navigator) {
  // API is available...
}  

Badges and Notifications

On iOS and iPadOS, badging has always been closely tied to notifications.

Apps built for macOS, iOS, and iPadOS are free to set their badge counts through platform specific APIs whenever they like. The user must grant the app permission to display notifications before the badge will appear.

Similarly, web apps are free to call setAppBadge() whenever they like, but the badge will only appear if the user has granted notifications permission.

To request notification permission, call Notification.requestPermission() as part of a user activation (such as a button click):

<button onclick="requestNotificationPermission()">
  Request notifications permission
</button>

<script>
  async function requestNotificationPermission() {
    const permission = await Notification.requestPermission();
    if (permission === 'granted') {
      // You can now use the Badging API
    }
  }
</script>

iOS and iPadOS will show this system prompt to the user for permission:

A browser permission prompt for asking the user to allow web notifications.

You can also check if a user has previously granted notifications permission by using the Permissions API:

async function checkNotificationPermission() {
  const permissionStatus = await navigator
    .permissions
    .query({ name: 'notifications' });

  switch (permissionStatus.state) {
    case 'granted':
      // You can use the Badging API
      break;
    case 'denied':
      // The user has denied the permission
      break;
    default:
      // The user has not yet granted or denied the permission
      await requestNotificationPermission();
      break;
  }
}
checkNotificationPermission();

Note that even though the user may have granted notifications permission, they remain in control of that permission through iOS or iPadOS Settings. As some users find badges distracting, they might leave the notifications permission enabled, but choose not to show the badge.

To retain user privacy, we never expose this user preference to the web app.

Setting and Clearing a Badge From a Web App

App badges represent the number of items requiring the user’s attention (e.g., “you have 5 unread messages“). What the number means is application dependent.

An application badge icon showing a badge with the number 5.

To update the application icon badge, pass a positive number to the navigator.setAppBadge() method:

async function setBadge(count) {
  if ('setAppBadge' in navigator) {
    try {
      await navigator.setAppBadge(count);
    } catch (error) {
      console.error('Failed to set app badge:', error);
    }
  }
}

// Set the badge count to 5
setBadge(5);

You can clear the badge by using navigator.clearAppBadge().

async function clearBadge() {
  if ('clearAppBadge' in navigator) {
    try {
      await navigator.clearAppBadge();
    } catch (error) {
      console.error('Failed to clear app badge:', error);
    }
  }
}

// Clear the badge
clearBadge();

Alternatively, calling navigator.setAppBadge(0) is equivalent to calling navigator.clearAppBadge().

Using the API From a Service Worker

In addition to being exposed to window objects, the Badging API is exposed in Web Worker contexts.

This makes it particularly useful for apps that support Web Push as it is trivial to update your application badge while your Service Worker handles a push event.

// Function to determine the badge count based on the event data
function determineBadgeCount(data) {
  // Process the data to compute the badge count
}

self.addEventListener('push', (event) => {
  let promises = [];

  if ('setAppBadge' in self.navigator) {
    const badgeCount = determineBadgeCount(event.data);
    // Promise to set the badge
    const promise = self.navigator.setAppBadge(badgeCount);
    promises.push(promise);
  }

  // Promise to show a notification
  promises.push(self.registration.showNotification("You've got mail!"));

  // Finally...
  event.waitUntil(Promise.all(promises));
});

When requesting a push subscription you must promise that all pushes will be user visible events. This means that each push event you process must result in a user visible notification being displayed with a call to self.registration.showNotification()

You are encouraged to update your application badge when handling a push event, but a badge update by itself does not fulfill the “user visible” requirement; Keep showing those notifications!

Security Restrictions for Third-Party

When the user added your web app to their Home Screen, they were indicating their trust in you as a first party.

JavaScript that calls navigator.setAppBadge() in an attempt to update the badge count must come from a frame that is the same-origin as your top-level document. Calls to navigator.setAppBadge() from cross-origin frames have no effect.

Evolving the Badging API at the W3C

We’re collaborating with the W3C Web Applications Working Group and its members, particularly with Microsoft who have partnered with us as co-editors of the Badging API specification. This collaboration allows us to create a more consistent, secure, and privacy-preserving API across browsers and operating systems. The Badging API specification was previously edited by folks from Google. We’re excited to work with the W3C membership to continue its evolution.

]]>
The User Activation API https://webkit.org/blog/13862/the-user-activation-api/ Wed, 15 Feb 2023 17:24:18 +0000 https://webkit.org/?p=13862 As a web developer, you’ve probably noticed that certain APIs only work if an end-user clicks or taps on an HTML element. For example, if you try to run the following code in Safari’s Web Inspector, it will result in an error:

await navigator.share({ text: "hi" });
NotAllowedError: The request is not allowed by the user agent or 
the platform in the current context, possibly because the user denied permission.

This error happens when code is not run as a direct result of the end-user clicking or tapping on an HTML element (e.g., a <button>).

Having code that runs as a result of an end-user action is what the HTML specification refers to as “user activation”. There are a large number of APIs on the web that depend on user activation. Common ones include:

  • window.open()
  • navigator.share()
  • navigator.wakelock.request()
  • PaymentRequest.prototype.show()
  • and there are many, many more…

So what constitutes a “user activation”?

The HTML spec defines the following events as “activation triggering user events”:

Together, this list effectively constitutes “user activation”. You’ll note the list of events above is really small. It’s restricted so that certain calls to APIs can only happen as a result of those very distinct end-user actions. This prevents end-users from being accidentally (or deliberately!) spammed with popup windows or other intrusive browser dialogs.

Now that we know about these special events, we can now write code to take into account user activation:

button.addEventListener("click", async () => {
   // This works fine...
   await navigator.share({text: "hi"});
});

So even though we are not specifically listening for a "mousedown" event, we know that the activation triggering event has occurred, so our code can run without throwing any errors.

End-user protections

Now you might be wondering, can one run execute multiple commands that require user activation insingle or multiple event listeners? Consider the following code sample:

button.addEventListener("click", async () => {

   // This works fine...
   await navigator.share({text: "hi"});

   // This will fail...
   window.open("https://example.com");
});

button.addEventListener("click", async () => {
   // This will now fail too...
   window.open("https://example.com");
});

But why do the calls to window.open() fail there? To understand that, we need to delve deeper into how browsers handle user activation under the hood.

Meet “transient” and “sticky” activation

When an “activation triggering user event” occurs what actually happens is that the browser starts an internal timer specifically tied to a browser tab. This timer is not directly exposed to the web page and runs for a short time (a few seconds, maybe). Each browser engine can determine how much time is allocated and it can change for a number of reasons (i.e., it’s deliberately not observable by JavaScript!). It’s designed to give your code enough time to perform some particular task (e.g., it could process some image data and then call navigator.share() to share the image with another application).

In HTML, this timer is called transient activation. And while this timer is running, that browser window “has transient activation”. HTML also defines a concept called sticky activation. This simply means that the web page has had transient activation at some point in the past.

Although rare, some APIs (e.g., Web Audio) use sticky activation to perform some actions.

Now, the above doesn’t explain why window.open() failed above. To understand why, we need to now discuss what HTML calls “activation-consuming APIs”.

APIs that “consume” the user activation

As the name suggests, “activation-consuming APIs” consume the user activation. That is, when those APIs are called, they effectively reset the transient activation timer, so the web page no longer has transient activation.

This behavior is why window.open() fails above: calling navigator.share() consumed the user activation, meaning that window.open() no longer had transient activation (so it fails).

A list of common APIs that consume transient activation in WebKit:

  • Web Notification’s requestPermission() method.
  • Payment Request: the show() method.
  • And, as we have already discussed, Web Share’s share() method.

This list is not exhaustive, and new APIs are being added to the web all the time that either rely on or consume transient activation.

As a point of interest: not all APIs consume the user activation. Some only require transient activation but won’t consume it. That allows multiple asynchronous operations dependent on user activation to take place. Otherwise, it would require the user to click or press on a button over and over again to complete a task, which would be quite annoying for them.

Scope of transient activation

A really useful thing to know about transient activation is that it’s scoped to the entire window (or browser tab)! That means that, so long as all iframes on a page are same-origin, they all have transient activation. However, for security reasons, cross-origin iframes will not have transient activation.

Transient activation across all same origin iframes

For third-party iframes to have transient activation, a user must explicitly activate an HTML element inside the third-party iframe. However, once they activate an element then transient activation propagates to the parent and to any iframes that are same origin to iframe where the activation took place:

Activation propagating to parent frame, to other iframes that match the third-party iframe

Security Note: you can (and should!) restrict what capacities third-party iframes have access to by setting the allow= and/or sandbox= attributes, as needed.

The UserActivation API

To assist developers with dealing with user activation, the HTML standard introduces a simple API to check if a page has transient and/or sticky activation.

  • navigator.userActivation.isActive:
    Returns true when the window has transient activation.
  • navigator.userActivation.hasBeenActive:
    Returns true if the window has had transient activation in the past (i.e., “sticky activation”).

So for example, you can do something like:

if (navigator.userActivation.isActive) {
    await navigator.share({text: "hi"})
}

Limitations and ongoing standards work

There are two significant limitations with the current user activation model that standards folks are still grappling with.
Firstly, consider the following case, where a file takes too long to download and the transient activation timer runs out:

button.onclick = () => {
    // Slow network + really big file
    const image = await fetch("really-big-file");

    // Oh no!!! transient activation expired! 😢
    navigator.share({files: [image]});
}   

There are ongoing discussions at the WHATWG and W3C about how we might address the problem above. Unfortunately, we don’t yet have a solution, but naturally we need some means to extend the transient activation so the code above doesn’t fail.

Secondly, there are legitimate use cases for enabling transient activation in a third-party iframe from a first-party document (e.g., to allow a third-party to process a request for payment). There is ongoing discussions to see if there is some means to safely enable third-party iframes to also have transient activation in special cases.

Automation and testing

To help developers deal with tricky edge-cases that could arise from the transient activation unexpectedly expiring, WebKit has been working with other browser vendors to allow the user activation to be consumed via Web Driver.

Conclusion

Web APIs being gated on user activation helps keep the user safe from annoying intrusions, like multiple popup windows or notification spam, while allowing developers to do the right thing in response to user interaction. The UserActivation API can help you determine if it’s OK to call a function that depends on user activation.

You can try out the User Activation API in Safari Technology Preview release 160 or later.

]]>
Declarative Shadow DOM https://webkit.org/blog/13851/declarative-shadow-dom/ Mon, 13 Feb 2023 18:10:29 +0000 https://webkit.org/?p=13851 We’re pleased to announce that support for the declarative shadow DOM API has been added and enabled by default in Safari Technology Preview 162. To recap, shadow DOM is a part of Web Components, a set of specifications that were initially proposed by Google to enable the creation of reusable widgets and components on the web. Since then these specifications have been integrated into the DOM and HTML standards. Shadow DOM, in particular, provides a lightweight encapsulation for DOM trees by allowing a creation of a parallel tree on an element called a “shadow tree” that replaces the rendering of the element without modifying its own DOM tree.

Up until this point, creating a shadow tree on an element required calling attachShadow() on the element in JavaScript. This meant that this feature was not available when JavaScript is disabled such as in email clients, and it required care to hide the content supposed to be in a shadow tree until relevant scripts are loaded to avoid flush of contents. In addition, many modern websites and web-based applications deploy a technique called “server-side rendering” whereby programs running on a web server generate HTML markup with the initial content for web browsers to consume, instead of fetching content over the network once scripts are loaded. This helps reducing page load time and also improves SEO because the page content is readily available for search engine crawlers to consume. Many server-side-rendering technologies try to eliminate the need for JavaScript for the initial rendering to reduce the initial paint latency and progressively enhance the content with interactivity as scripts and related metadata are loaded. This was, unfortunately, not possible when using shadow DOM because of the aforementioned requirement to use attachShadow().

Declarative shadow DOM addresses these use cases by providing a mechanism to include shadow DOM content in HTML. In particular, specifying a shadowrootmode content attribute on a template element tells web browsers that the content inside of this template element should be put into a shadow tree attached to its parent element. For example, in the following example, the template element with shadowrootmode will attach a shadow root on some-component element with a text node containing “hello, world.” as its sole child node.

<some-component>
    <template shadowrootmode="closed">hello, world.</template>
</some-component>

When scripts are loaded and ready to make this content interactive, the shadow root can be accessed via ElementInternals as follows:

customElements.define('some-component', class SomeComponent extends HTMLElement {
    #internals;
    constructor() {
        super();
        this.#internals = this.attachInternals();

        // This will log "hello, world."
        console.log(this.#internals.shadowRoot.textContent.trim());
    }
});

We designed this API with backwards compatibility in mind. For example, calling attachShadow() on an element with a declarative shadow DOM returns the declaratively attached shadow root with all its children removed instead of failing by throwing an exception. It means that adopting declarative shadow DOM is backwards compatible with existing JavaScript which relies on attachShadow() to create shadow roots. Note that none of the JavaScript parser APIs (such as DOMParser and innerHTML) support declarative shadow DOM by default to avoid creating new cross-site scripting vulnerabilities in existing websites that accepts arbitrary template content (since script elements in such content had been previously inert and would not run).

In addition, we’re introducing the ability to clone shadow roots. Until now, ShadowRoot and its descendant nodes could not be cloned by cloneNode() or importNode(). attachShadow() now takes cloneable flag as an option. When this flag is set to true, existing JavaScript API such as cloneNode() and importNode() will clone ShadowRoot when cloning its shadow host. Declarative shadow DOM automatically sets this flag to true so that declarative shadow DOM which appears inside other template elements can be cloned along with its host. In the following example, the outer template element contains an instance of some-component element and its shadow tree content is serialized using declarative shadow DOM. Cloning template1.content with document.importNode(template1.content, true) will clone some-component as well as its (declaratively defined) shadow tree.

<template id="template1">
    <some-component>
        <template shadowrootmode="closed">hello, world.</template>
    </some-component>
</template>

In summary, declarative shadow DOM introduces an exciting new way of defining a shadow tree in HTML, which will be useful for server-side rendering of Web Components as well as in context where JavaScript is disabled such as email clients. This has been a highly requested feature with lots of discussions among browser vendors. We’re happy to report its introduction in Safari Technology Preview 162.

]]>
ElementInternals and Form-Associated Custom Elements https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/ Mon, 06 Feb 2023 20:51:56 +0000 https://webkit.org/?p=13711 In Safari Technology Preview 162 we enabled the support for ElementInternals and the form-associated custom elements by default. Custom elements is a feature which lets web developers create reusable components by defining their own HTML elements without relying on a JavaScript framework. ElementInternals is a new addition to custom elements API, which allows developers to manage a custom element’s internal states such as default ARIA role or ARIA label as well as having custom elements participate in form submissions and validations.

Default ARIA for Custom Elements

To use ElementInternals with a custom element, call this.attachInternals() in a custom element constructor just the same way we’d call attachShadow() as follows:

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);

Here, #internals and #shadowRoot are private member fields. The above code will define a simple custom element whose ARIA role is button by default. Achieving the same effect without using ElementInternals required sprouting ARIA content attribute on the custom element itself like this:

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);

This code is problematic for a few reasons. For one, it’s surprising for an element to automatically add content attributes on itself since no built-in element does this. But more importantly, the above code prevents users of this custom element to override ARIA role like this because the constructor will override the role content attribute upon upgrades:

<some-button role="switch"></some-button>

Using ElementInternals’s ariaRole property as done above, this example works seamlessly. ElementInternals similarly allows specifying the default values of other ARIA features such as ARIA label.

Participating in Form Submission

ElementInternals also adds the capability for custom elements to participate in a form submission. To use this feature of custom elements, we must declare that a custom element is associated with forms as follows:

class SomeButtonElement extends HTMLElement {
    static formAssociated = true;
    static observedAttributes = ['value'];
    #internals;
    constructor()
    {
        super();
        this.#internals = this.attachInternals();
        this.#internals.ariaRole = 'button';
    }
    attributeChangedCallback(name, oldValue, newValue)
    {
        this.#internals.setFormValue(newValue);
    }
}
customElements.define('some-button', SomeButtonElement);

With the above definition of a some-button element, some-button will submit the value of the value attribute specified on the element for the name attribute specified on the same element. E.g., if we had a markup like <some-element name="some-key" value="some-value"></some-element>, we would submit some-key=``some-value.

Participating in Form Validation

Likewise, ElementInternals adds the capability for custom elements to participate in form validation. In the following example, some-text-field is designed to require a minimum of two characters in the input element inside its shadow tree. When there are less than two characters, it reports a validation error to the user using the browser’s native UI using setValidity() and reportValidity():

class SomeTextFieldElement extends HTMLElement {
    static formAssociated = true;
    #internals;
    #shadowRoot;
    constructor()
    {
        super();
        this.#internals = this.attachInternals();
        this.#shadowRoot = this.attachShadow({mode: 'closed', delegatesFocus: true});
        this.#shadowRoot.innerHTML = '<input autofocus>';
        const input = this.#shadowRoot.firstChild;
        input.addEventListener('change', () => {
            this.#internals.setFormValue(input.value);
            this.updateValidity(input.value);
        });
    }
    updateValidity(newValue)
    {
        if (newValue.length >= 2) {
            this.#internals.setValidity({ });
            return;
        }
        this.#internals.setValidity({tooShort: true}, 
            'value is too short', this.#shadowRoot.firstChild);
        this.#internals.reportValidity();
    }
}
customElements.define('some-text-field', SomeTextFieldElement);

With this setup, :invalid pseudo class will automatically apply to the element when the number of characters user typed is less than 2.

Form-Associated Custom Element Callbacks

In addition, form-associated custom elements provide the following set of new custom element reaction callbacks:

  • formAssociatedCallback(form) – Called when the associated form element changes to form. ElementInternals.form returns the associated from element.
  • formResetCallback() – Called when the form is being reset. (e.g. user pressed input[type=reset] button). Custom element should clear whatever value set by the user.
  • formDisabledCallback(isDisabled) – Called when the disabled state of the element changes.
  • formStateRestoreCallback(state, reason) – Called when the browser is trying to restore element’s state to state in which case reason is “restore”, or when the browser is trying to fulfill autofill on behalf of user in which case reason is “autocomplete”. In the case of “restore”, state is a string, File, or FormData object previously set as the second argument to setFormValue.

Let’s take a look at formStateRestoreCallback as an example. In the following example, we store input.value as state whenever the value of input element inside the shadow tree changes (second argument to setFormValue). When the user navigates away to some other page and comes back to this page, browser can restore this state via formStateRestoreCallback. Note that WebKit currently has a limitation that only string can be used for the state, and “autocomplete” is not supported yet.

class SomeTextFieldElement extends HTMLElement {
    static formAssociated = true;
    #internals;
    #shadowRoot;
    constructor()
    {
        super();
        this.#internals = this.attachInternals();
        this.#shadowRoot = this.attachShadow({mode: 'closed', delegatesFocus: true});
        this.#shadowRoot.innerHTML = '<input autofocus>';
        const input = this.#shadowRoot.querySelector('input');
        input.addEventListener('change', () => {
            this.#internals.setFormValue(input.value, input.value);
        });
    }
    formStateRestoreCallback(state, reason)
    {
        this.#shadowRoot.querySelector('input').value = state;
    }
}
customElements.define('some-text-field', SomeTextFieldElement);

In summary, ElementInternals and form-associated custom elements provide an exciting new way of writing reusable component that participates in form submission and validation. ElementInternals also provides the ability to specify the default value of ARIA role and other ARIA properties for a custom element. We’re excited to bring these features together to web developers.

]]>
Allowing Web Share on Third-Party Sites https://webkit.org/blog/13708/allowing-web-share-on-third-party-sites/ Tue, 31 Jan 2023 17:27:14 +0000 https://webkit.org/?p=13708 As of Safari Technology Preview 160, it is no longer possible to use the W3C’s Web Share API with third-party sites within an iframe without including an allow attribute. All browser vendors agreed to this change as part of the W3C’s standardization process, and it is being rolled out in all major browser engines (including Chrome, Edge, Firefox, on mobile and desktop browsers).

The Web Share API allows web developers to enable the native sharing functionality of a device, such as sharing a link via email or social media. Prior to this change, the API could be used on any website within an iframe without restriction. However, due to concerns about privacy and security, browser vendors at the W3C have decided to limit the use of the API to only those sites that have explicitly been given permission to use it.

Web developers must now include an allow attribute in the iframe HTML element to use the Web Share API within an iframe on a third-party site. The attribute accepts a value of web-share and optionally the origin of the site that is allowed to use the API.

<iframe allow="web-share" src="https://example.com">
</iframe>

or

<iframe allow="web-share https://example.com" src="https://example.com">
</iframe>

Without the allow attribute, the API will throw an exception and will not function within the iframe. The syntax of the allow attribute is defined by the W3C’s Permissions Policy specification. You can learn more about the syntax on MDN.

This change is a necessary step in protecting user privacy and security. It helps ensure that the Web Share API is only used by sites that the developer signals is trustworthy. However, that means web developers will need to make some code changes to continue using the API within third-party iframes.

]]>
Non-interactive Elements with the inert attribute https://webkit.org/blog/12578/non-interactive-elements-with-the-inert-attribute/ Tue, 26 Apr 2022 17:21:18 +0000 https://webkit.org/?p=12578 Elements are interactive in multiple ways; they can be focused, clicked, edited or selected. Assistive technologies such as screen readers also interact with them. What if you wanted to disable all these interactions for a section of your webpage? The inert attribute covers this.

Pre-existing Solutions

The most basic way to disable all interactions is the disabled HTML attribute, it prevents focus, clicks, edition or selection. However, it only works on form controls, and does not necessarily hide the control from assistive technologies. What if you wanted to do this on elements other than form controls?

To prevent focus, tabindex="-1" is a popular option, but it isn’t ideal because setting the attribute on a container element will not prevent its children from being focused, and it also does not prevent click-focus with a mouse. If you want to prevent keyboard focus on a whole section, every single focusable element in the section needs the attribute set.

To prevent clicks, the pointer-events: none CSS property may be an option although, unless you’ve put tabindex="-1" on the element as well, tabbing to the element and interacting with it is still possible.

To prevent selection, the user-select: none CSS property is the most common option.

To prevent editing, if you’re using contenteditable, you would need to manually remove the contenteditable attribute. If it’s a form control, you need to manually set the disabled attribute.

All of this is heavy and also does not allow you to hide the section from assistive tools, which may be appropriate when making a whole section non-interactive. It is currently possible to do this by setting aria-hidden="true" on the relevant section.

This is why the inert attribute was added to HTML, it is an efficient way to disable all of the interactions above and hide elements from assistive technology.

What is the inert attribute?

The inert attribute was originally specified in the context of the dialog element about 10 years ago, where the main use-case was making the content behind a modal dialog non-interactive. It was later removed due to a lack of adoption. Recent developer requests lead it to be re-proposed a few years later. However, the main issue with that definition was complexity in interaction with modal dialogs and performance. This is why changes have been made to the standard to use a CSS-based approach to define inert nodes.

According to the HTML specification, when a node is inert:

  • Hit-testing must act as if the ‘pointer-events’ CSS property were set to ‘none’.
  • Text selection functionality must act as if the ‘user-select’ CSS property were set to ‘none’.
  • If it is editable, the node behaves as if it were non-editable.

In addition to the above:

  • It is not focusable.
  • It behaves similarly to aria-hidden="true" for assistive technologies

Example Usage: A Carousel

Let’s build a carousel with multiple pages:

You will notice that you can still interact with links/inputs/etc. from inactive pages. This is undesirable behavior that can be prevented with the inert attribute. In the switchToIndex method which changes the active page, we can add 2 lines of code that fix this.

this.items being an array of HTML elements representing each slide, the first line makes every slide inert, and the second line makes the active one non-inert.

  switchToIndex(index) {
    [...]
    this.items.forEach(item => item.inert = true);
    this.items[index].inert = false;
  }

Notice that we have used the inert DOM property. When set to a value, it is equivalent to element.toggleAttribute("inert", value) .

For support for in older browsers, you may want to use a polyfill until the inert attribute is widely supported.

Finally it is recommended to add a visual cue that a certain element is inert. Let’s make inactive slides less opaque:

.carousel-item[inert] {
  opacity: 0.6;
}

Here is the final result in action:

As you can see above, inactive slides are no longer interactive but they are still visible.

Browser support

Additional resources

In this post, we gave an overview of the new inert attribute. Here are some interesting additional resources you might want to check:

Feel free to reach out to @therealntim on Twitter for any questions. To report any issues, please file a bug blocking bug 165279.

]]>
Working together on Interop 2022 https://webkit.org/blog/12288/working-together-on-interop-2022/ Thu, 03 Mar 2022 17:00:44 +0000 https://webkit.org/?p=12288 From the very beginning, the web was always intended to work in any browser, on any computer. This is possible through interoperability — when each underlying web technology is implemented in the same way in every browser. To reach interoperability, it takes a commitment from all browser engineers to implement web technology according to web standards — the incredibly detailed specifications where new technology is defined.

In 2022, Apple, Bocoup, Google, Igalia, Microsoft, and Mozilla have come together to commit to improve interoperability in 15 key areas that will have the most impact on web developer experience, in a project called Interop 2022.

At its root, Interop 2022 is an evolving metric generated from a set of automated tests that aims to evaluate support for certain web standards that are most important for web developers. The Interop 2022 dashboard will constantly update throughout the year, showing progress as browser engineers fix bugs, implement new features, and improve the tests.

a screenshot of the Interop 2022 dashboard, showing starting scores of: Chrome and Edge Dev, 71. Firefox Nightly, 74. And Safari Technology Preview: 73.
The current overall score on the Interop 2022 dashboard on March 3, 2022.

The group planning Interop 2022 chose ten new focus areas to add to the five areas from Compat 2021. We also committed to several investigation projects, which will begin this spring.

a screenshot of the Interop 2022 dashboard, showing the data table of the 15 focus areas, with the percentage supported for each browser
The scoring breakdown on the Interop 2022 dashboard on March 3, 2022.

Focus areas

Chosen from a list of proposals, with an eye to what web designers & developers want and need most, the ten new focus areas for 2022 are:

  1. Cascade Layers
  2. Color Spaces and Functions
  3. Containment
  4. Dialog Element
  5. Form Fixes
  6. Scrolling
  7. Subgrid
  8. Typography and Encodings
  9. Viewport Units
  10. Web Compat

Let’s take a quick tour of each.

Cascade Layers

Designed to soothe the frustrations of web developers wrestling with CSS on large projects, Cascade Layers provides a powerful way to organize styles into layers, where specificity is calculated independently inside each layer.

a diagram of cascade layers, showing how Author layers cascade

A website could create a “framework” layer and a “custom” layer — assigning all the CSS from a 3rd-party framework to framework layer, writing their own code in the custom layer. They could designate that everything in the custom layer should beat everything in the framework layer, no matter the specificity of the selectors being used.

Color Spaces and Functions

In the early days of the web, most sites restricted their use of color to a specific palette of 216 colors. Then for a long time, web developers used anything in the sRGB color space, and typically expressed those colors in hexadecimal, rgb(), rgba(), or hsl(). Meanwhile, camera and monitor technology have greatly evolved to capture and display a wider and brighter range of colors. Today’s Apple displays support the Display P3 color space, which is about 50% wider than sRGB.

New color functions and support for new color spaces bring this vibrancy to the web. Interop 2022 includes testing for support of three expanded color spaces (LAB, LCH, P3), and two ways to write color in CSS through functional notation: color-mix and color-contrast.

Learn more about color spaces and functions in Improving Color on the Web, Wide Gamut Color in CSS with Display-P3, and Wide Gamut 2D Graphics using HTML Canvas.

Containment

For several years now, web developers’ number one most requested addition to the web has been Container Queries. It will be a powerful tool in CSS for identifying and measuring the size of a specific container, and then conditionally applying styles based on that size. It’s like media queries, but instead of measuring the size of the viewport, you measure the size of a box holding the content.

Containment is foundational to making Container Queries work. In fact, Container Queries is defined in level 3 of the Containment specification. The group driving Interop 2022 didn’t come to consensus to include Container Queries this year. But we did agree to focus on the interoperability of layout, size, and paint containment through the containment property, setting the stage for prioritizing the interoperability of the rest of Containment and Container Queries in the future.

Dialog Element

Another long-requested feature for the web, the dialog element provides a robust and powerful way to create overlays and modals. The ::backdrop pseudo-element makes it possible to style the background underneath the modal. You can learn more about how to use <dialog> and ::backdrop in Introducing the Dialog Element.

Form Fixes

Forms are another area where web designers and developers find interoperability challenges — ones that the Open UI community group and appropriate standards bodies are working to solve. Interop 2022 is contributing to this work by focusing on improving the pass rates for existing tests of existing specs. This includes the appearance property, <form>, events on disabled form controls, and bugs with input elements, form submission, and form validation.

Scrolling

Today’s websites and web apps care more deeply about how scrolling works than ever before. Scroll snap provides the tools for designers and developers to control how interfaces scroll and how content appears. The scroll-behavior property in CSS sets the behavior for a scrolling box when scrolling is triggered by the navigation or CSSOM scrolling APIs. The overscroll-behavior CSS property determines what a browser does when reaching the boundary of a scrolling area.

Subgrid

CSS Grid shipped five years ago in March 2017, revolutionizing what’s possible in layout design on the web. Subgrid is defined in CSS Grid level 2, and provides an easy way to put grandchildren of a grid container on that grid. It will make it possible to line up items across complex layouts, without any regard for the DOM structure. The vision of a working layout system on the web will be more fully realized with Grid and Subgrid together.

Typography and Encodings

Typography and encodings encompasses a collection of tests that impact typography on the web. Font Features are powerful properties for refining typography, but incomplete support has been making them harder to use then they’re supposed to be. The vast majority of the encoding tests pass in every browser, but a handful do not, so they’ve been included. And the ic unit is included.

Viewport Units

Web developers often ask for a tool that would work similar to viewport units, but work better on mobile devices where the dimensions of the browser’s viewport change as a user scrolls the page. The new Viewport Units are that solution. 100svh refers to 100% of the height of the smallest possible viewport. 100lvh refers to 100% of the height of the largest possible viewport. 100dvh refers to 100% of the dynamic viewport height — meaning the value will change as the user scrolls.

100svh measures the smallest viewport, top to bottom. 100lvh measures the largest viewport. 100dvh measures the dynamic viewport, changing as the user scrolls.

There are other new viewport units as well — svw, lvw, and dvw serve the same purpose, for width. And there are new units to refer to the inline or block dimensions of the viewport.

Web Compatibility

There are many scenarios that could impact web compatibility. For example, specific bugs in browsers could disproportionately cause some websites to not work as intended, or perhaps one browser may vary from the web standard, causing an inconsistent and buggy experience for website or web app users. Interop 2022 aims to capture and address these issues through the web compatibility measurement.

Investigation Projects

There were several other items that the group knows are important to web development, but which cannot yet be easily evaluated by automated testing. We’ve committed to embark on several investigations in these areas — to manually test browsers, to determine if and how automated testing could be more helpful, to improve the infrastructure of WPT itself, to discover where there might still be a lack of interop, and to make suggestions to the appropriate standards groups. The three areas of investigation are:

  • Editing, contenteditable, and execCommand
  • Pointer and Mouse Events
  • Viewport Measurement

Each browser will always have the same result for “2022 Investigation”, based on how much work has been accomplished by the group as a whole.

Our Commitment to Interoperability

All of these technologies are important to Apple and to everyone working on WebKit. We care deeply about the health of the web, and interoperable implementations of web standards. We welcome collaboration with our colleagues in the many web standards organizations, and in Interop 2022 to make the web as interoperable as it can be. Because that’s how websites and web apps will work best for the people who matter most — everyday people using the web to live their lives.

For more

Read more about Interop 2022 in articles from Bocoup, Google, Igalia, Microsoft, and Mozilla.

]]>