September 21, 2022

Release Notes for Safari Technology Preview 154

Surfin’ Safari

Safari Technology Preview Release 154 is now available for download for macOS Monterey 12.3 or later and macOS Ventura beta. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

This release includes WebKit changes between: 253848@main…254351@main

Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

Web Inspector

  • Sources Tab
  • Elements Tab
    • Added “Scroll” labels in the element tree for scrollable elements (254061@main)
    • Added warnings in the Font details sidebar for synthesized boldness or obliqueness (254188@main)
    • Fixed DOM Tree to update when a top-level item in the DOM tree is added or removed (254062@main)
    • Prevented showing the Target in the Event badge popover (254018@main)
  • Timelines Tab
    • Fixed selecting timeline records from a coalesced record bar selecting the first record in the group, not the record nearest the cursor. (254056@main)
  • Settings
    • Added a setting to control whether nodes that are not rendered are de-emphasized (254022@main)



  • Fixed ShadowRealm test262 failures (253977@main)
  • Finished to and from methods for Temporal.PlainDate[Time] (Behind the --useTemporal flag) (253876@main)
  • Optimized Proxy [[Get]] operations (254092@main)
  • Optimized String#replace(String, String) (254156@main)
  • Optimizde String#replace with constant String via Boyer-Moore-Horspool search algorithm (254342@main)


  • Fixed incorrect bidi visual direction after forced line break (253872@main)
  • Fixed <div> with border-radius not getting repainted correctly when the bounds change (254041@main)
  • Fixed border-radius clipping of composited layers (254253@main)
  • Fixed updating transforms on table sections (254351@main)

Reporting API

  • Added support for generateTestReport in WebDriver (254122@main)
  • Hooked up to Content-Security-Policy 'report-to' directive (253966@main)
  • Refactored network send logic for Report-related code (254208@main)


  • Added support for COOP navigation violation reporting (254291@main)
  • Added support for ElementInternals.role, ariaLabel, and ariaRoleDescription (254278@main)
  • Added support for PermissionStatus.onchange (254193@main)
  • Added support for InputEvent.isComposing (254131@main)
  • Added ReferrerPolicy to the PolicyContainer (254003@main)
  • Added generation of IndexedDB object serializers (254196@main)
  • Enabled Scroll To Text Fragment by default (253855@main)
  • Enabled SKAttributionEnabled by default (254328@main)
  • Changed to not parse or scroll to text fragments in iframe URLs (254073@main)
  • Fixed memory usage scaling for Compression Streams to handle out-of-memory conditions (253930@main)
  • Fixed deleting a button element leaving the style inside the button element (254285@main)
  • Fixed matching fancy quotes and apostrophes for Scroll To Text Fragment fragments (254275@main)
  • Fixed link elements to be able to fire more than one load or error event (254290@main)
  • Fixed String.prototype.includes incorrectly returning false when the string is empty and the position is past end of string (254319@main)
  • Fixed opening the share sheet to not clear the text selection (254136@main)
  • Fixed <canvas> ConicGradient angle to start at the x-axis, not at the top (254038@main)
  • Fixed X-Frame-Options HTTP headers with an empty value being incorrectly ignored (254245@main)
  • Stopped painting a border for images with loading="lazy" before it loaded (253960@main)


  • Implemented WebVTT-based audio descriptions with text-to-speech (253931@main, 254266@main)
  • Implemented the ARIA 1.3 mark role, which provides parity with the <mark> tag (254008@main)


  • Fixed AudioWorklet scripts to inherit their owner document’s referrer policy (254306@main)
  • Fixed canPlayType for WebM VP8 on older Macs without VP9 (254013@main)
  • Fixed WebM files that fail to play (254219@main)
  • Fixed WebVTT styles not getting applied with in-band tracks (254109@main)

Intelligent Tracking Prevention

  • Updated to wait longer before deleting website data for domains where a user interacted with a Web Push notification (254048@main)

September 21, 2022 08:00 PM

September 12, 2022

WebKit Features in Safari 16.0

Surfin’ Safari

Today, we are excited to announce the release of Safari 16.0 for iOS 16, macOS Monterey and macOS Big Sur. This release contains quite a few new web technologies that web developers can use to make their sites and web apps even better.

To update to Safari 16.0 on macOS Monterey and macOS Big Sur, go to System Preferences → Software Update → More info. To update to Safari 16.0 on iOS, install iOS 16. Safari 16 for macOS Ventura and iPadOS 16 are coming this October, and will include Web Push on macOS Ventura.

a word cloud of everything that shipped in Safari this year, including what's in Safari 16

We announced many details about what’s in Safari 16 in our article, News from WWDC22: WebKit Features in Safari 16 Beta, and the WWDC22 session, What’s new in Safari and WebKit (32 min video). But that’s not all. In the months since WWDC, we’ve added even more.

New since Safari 16 Beta 1

Safari for iOS 16 includes support for still images compressed using the AVIF format. Developed by the Alliance for Open Media, AVIF is an alternative to image formats like JPEG, PNG, GIF, or WebP. It offers multiple color spaces, lossless and lossy compression, and more. Support for AVIF will also come to macOS Ventura and iPadOS in October.

WebKit now fully supports the resolution media query. This media query provides a way for web developers to conditionally apply CSS based on the pixel density of a screen. For example: @media (min-resolution: 326dpi) { }.

WebKit now supports text-align-last, a CSS property that sets how the last line of a text block is aligned. For instance, a paragraph could have text-align: center applied to most its lines, while the last line of that paragraph is aligned right with text-align-last: right.

The :has() pseudo-class in WebKit now supports :target. The CSS :target pseudo-class selects an element when that element has an id that matches a fragment in the URL. For example, if a user clicks on a link that takes them to, and an element on that page has the ID #chapter2, then the :target pseudo-class will select that element. In Safari 16, :has(:target) opens up new possibilities when using URLs with fragments. For an in-depth look at :has, read Using :has() as a CSS Parent Selector and much more.


Safari on iOS 16 adds support for passkeys. They provide users with an incredibly easy way to log in, while delivering a profound increase in security.

To sign in with a passkey, fill in your username (or email, depending on the site) and tap the button. Your device authenticates that it’s you, and you’re in.

The technology that makes passkeys possible is defined in open standards from the FIDO Alliance and the W3C, including the WebAuthn standard, which already has widespread support in browsers. Passkeys are an industry-wide effort, and “passkeys” is a common noun, to be used by anyone. You can offer passkeys alongside your existing authentication options. First, teach your backend to store public keys and issue authentication challenges. Then, on your website or web app, offer passkeys to users by adopting the APIs for creating new passkeys and allowing users to sign in with their passkey.

If your website or web app already supports using a platform authenticator with WebAuthn, there are a few things to note as you add support for passkeys. Make sure you aren’t limiting signing in to the device that saved the passkey; that is, don’t use a cookie to “remember” that a user set up a key on a particular device. Also, make sure the username fields in your existing sign-in forms are compatible with AutoFill by adopting “conditional mediation”. Finally, start to refer to passkeys, and treat them as a primary way to sign in.

To learn more, watch the WWDC22 session, Meet Passkeys (27 min video) or read Supporting passkeys. In October, support for passkeys will come to macOS Monterey and macOS Big Sur, as well as macOS Ventura and iPadOS.

Apple Pay

Safari 16 adds Apple Pay support for Merchant Tokens, a more efficient way to support recurring payments, and support for multi-merchant payments, a way to pay multiple merchants of record in one transaction. Safari 16 also supports Order Tracking to enable merchants on the web to provide detailed order and shipping information in Wallet. Apple Pay can now be used in all WKWebView.

Web Inspector Extensions

Safari 16 brings support for Web Inspector Extensions, enabling you to enhance Safari’s built-in web developer tools. Download these extensions on macOS by going to Safari > Safari Extensions and looking for Web Inspector Extensions in the App Store. Search for developer tools from your favorite third-party developer services, test suites, and frameworks — including Angular DevTools, which recently announced support. If you’d like to learn how to make such extensions, watch the WWDC22 session, Create Safari Web Inspector Extensions (18 min video).

Safari 16 includes even more for Safari Web Extensions, including the ability to sync which extensions are enabled across iOS, iPadOS, and macOS. Safari 16 supports both manifest version 2 and manifest version 3. Watch What’s new in Safari Web Extensions from WWDC22 to learn about the differences, and how to upgrade your extension. Web Extensions in Safari 16 also add support for declarativeNetRequestWithHostAccess permission and browser.runtime.getFrameID.

Container Queries

Similar to Media Queries, Container Queries allow you to adjust the layout or styling of a particular item on your web page based on the size of its container rather than the size of the viewport. Safari 16 supports size queries and container query units. “Size queries” are what web developers imagine when they talk about Container Queries — the opportunity to write CSS that only applies if a container is a certain size. Container Query Units are similar to Viewport Units, but they specify a length relative to the dimensions of a query container instead of the viewport. These include cqw, cqh, cqi, cqb, cqmin, and cqmax.


CSS Grid revolutionized what’s possible in layout design on the web. Subgrid takes Grid to another level, providing an easy way to put grandchildren of a grid container on that grid. It makes it possible to line up items across complex layouts without being constrained by the HTML structure. And Safari’s Grid Inspector lets you turn on the overlays for as many grids as you want — which is especially helpful when coding subgrid.

Web Inspector

Following last year’s addition of Grid Inspector, Safari 16.0 adds Flexbox Inspector. It pairs perfectly with the addition of Alignment Editor in Safari 15.4.

a screenshot of the Flexbox Inspector in action, drawing lines around the container, around each item, and marking gaps and free spaceOverlays for Flexbox containers make it easier to visualize the effects your CSS.

Safari’s Flexbox Inspector visually distinguishes between excess free space and Flex gaps. It also shows the boundaries of items, revealing how they are distributed both on the main axis and the cross axis of your Flexbox containers. The toggleable “Order Numbers” option show the layout order of elements in the container, which can be helpful when using the order property. And just like our overlays for Grid last year, you can simultaneously show as many Flexbox overlays as you want, without any impact on scrolling performance. A single checkbox turns them all on.

In the Timelines Tab there are additional links to reference documentation, and a new experimental Screenshots timeline that captures screenshots of the viewport when content changes are painted.

The Elements Tab now supports showing Container Queries in the Styles sidebar.

The Sources Tab brings new improvements including allowing local overrides for requests to use regular expression matches in the redirect URL and local overrides for responses now being able to be mapped to a file on disk.

The Network Tab includes a new proxy indicator, and provides a way to entirely block network requests.

Accessibility Improvements

Safari 16 introduces a re-architecture of WebKit’s accessibility support on macOS. By adding Isolated Tree Mode, WebKit reduces VoiceOver hangs by offloading accessibility work to a secondary thread, improving performance and increasing responsiveness. This change allows WebKit to service more accessibility requests from clients like VoiceOver in less time than before. On some complex webpages, we’ve measured twice the number of accessibility requests served in twenty-five percent less time. Safari 16 also greatly improves accessibility support for elements with display:contents by ensuring they are properly represented in the accessibility tree.

Animation Improvements

WebKit now supports CSS Offset Path (also known as Motion Path), providing web developers the ability to animate objects along a custom path of any shape. The offset-path property lets you define a geometrical path along which to animate. The offset-anchor, offset-distance, offset-position, and offset-rotate properties give you additional abilities to refine the exact movement of the object being animated. While the offset property acts as a shorthand for combining these properties.

In Safari 16, you can now animate track sizes in CSS Grid, dynamically changing the size of rows and columns.

Safari 16 also adds support for composite operations, resolving how an element’s animation impacts its underlying property values. And it adds support for discrete animation to thirty-nine CSS properties.

Overscroll Behavior

CSS Overscroll Behavior determines what happens when a user scrolls and reaches the boundary of a scrolling area. It’s useful when you want to stop scroll chaining — when a user scrolls inside a box and hits the end, you now have control over stopping or allowing scrolling on the rest of the page.

Shared Workers

WebKit now supports Shared Workers. It’s similar to Service Workers, running JavaScript in the background, but its lifetime is slightly different. Your Shared Worker runs as long as the user has any tab open to your domain. All the tabs open to the same domain can share the same Shared Worker.

Additional Features

Safari 16 adds support for Shadow Realms, <form>.requestSubmit(), the showPicker() method for HTML input elements, and the worker-src Content Security Policy directive.

Fixes and Polish

Safari 16.0 also contains quite a few bug fixes and feature polish.


  • Fixed gradient color interpolation with alpha.
  • Removed most non-standard CSS appearance values.
  • Polished the implementation of the :has() pseudo-class, updated to match the evolving web standard.
  • Polished the implementation of Cascade Layers.


  • Fixed firing the load event after a form is submitted with a "_blank" target.
  • Fixed contenteditable anchors getting stuck with an :active state.
  • Fixed changing the value for stepUp() and stepDown() with out-of-range values.
  • Fixed using min as the default value when min is greater than max for <input type="range">.
  • Fixed making value updates visible for <input type="email">.
  • Fixed making sure :active is removed on keyup event for radio inputs.
  • Fixed applyStep() behavior to align with specifications.
  • Fixed the select() method returns to align with specifications.
  • Fixed form data generated by <input type="image"> when a value attribute is present.
  • Fixed the FormData object to not include an entry for the submit button used to submit the form.
  • Fixed returning an empty string for invalid floating-point numbers that end with a ".".
  • Fixed selection range after the type attribute of an <input> changes.
  • Fixed selection range to be limited by the length of the current value.
  • Fixed the user-agent stylesheet to include table { text-indent: initial } to align with specifications.
  • Fixed the user-agent stylesheet to include box-sizing: border-box for <input type="color">.
  • Fixed the line-height declaration to use !important for the placeholder of an input.


  • Fixed blocking image content in object elements.
  • Fixed incorrect CORP/COEP check in 304 responses.
  • Fixed mixing strict-dynamic and unsafe-inline policies.
  • Fixed script-src-elem policies in Workers.
  • Fixed incorrect blocked-uri for inline scripts and strict-dynamic policies.

WebGL 2

  • Fixed handling AllowShared TypedArray.

Web Inspector

  • Fixed breakpoints not triggering, breakpoints occurring at incorrect locations in scripts, and incorrect error/stack trace line/column numbers when inspecting minified sources with multi-line strings.
  • Fixed importing Timelines sometimes not scrolling.
  • Fixed importing Audits sometimes crashing.
  • Fixed CSS autocomplete to suggest the most commonly-used property, not the alphabetical one.

Web Driver

  • Fixed pointerMove actions to correctly fire mouse events.
  • Fixed rapid session creation and deletion leading to timeouts in session creation.

Safari Web Extensions

  • Fixed incorrect counts being returned from getBytesInUse.
  • Extensions that request the unlimitedStorage permission no longer also need the storage permission.
  • Updated the maximum number of static rulesets to 50 and the maximum number of enabled static rulesets to 10.
  • Service workers are no longer returned from extension.getBackgroundPage and extension.getViews


We love hearing from you. Send a tweet to @webkit, @jensimmons, or @jonathandavis to share your thoughts on Safari 16. If you run into any issues, we welcome your feedback on Safari UI, or your WebKit bug report about web technology or Web Inspector. Filing issues really does make a difference.

Download the latest Safari Technology Preview to stay at the forefront of the web platform and to use the latest Web Inspector features. You can also use the WebKit Feature Status page to watch for new information about the web features that interest you the most.

To learn more about what’s in Safari 16 for web developers, read the Safari 16 Release Notes.

September 12, 2022 05:45 PM

Introducing JetStream 2.1

Surfin’ Safari

JetStream 2 is a JavaScript benchmark suite that runs 64 subtests. We recently discovered that the benchmark score of JetStream 2 may be influenced by pause times between individual subtests because of second order effects, like CPU ramping. Network latency is one source of such pause times. As a result, depending on the speed of one’s network, JetStream 2’s benchmark score can vary. This is undesirable since the goal of JetStream 2 is to measure JavaScript performance, not the second order effects of network latency.

We’re introducing JetStream 2.1 which runs the same subtests as JetStream 2. However, JetStream 2.1 updates the benchmark driver to pre-fetch network resources into Blobs, and create URLs from them. The subtests will now load resources from these blob URLs instead.

With this change, we measured JetStream 2.1’s benchmark scores on both Chrome and Safari to be more stable and is less perturbed by pause times due to network latency. Scores produced from JetStream 2.1 are not comparable to other versions of any JetStream benchmark.

September 12, 2022 04:50 PM

September 07, 2022

Release Notes for Safari Technology Preview 153

Surfin’ Safari

Safari Technology Preview Release 153 is now available for download for macOS Monterey 12.3 or later and macOS Ventura beta. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

This release includes WebKit changes between: 253169@main…253847@main

Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

Web Inspector

  • Sources Tab
  • Elements Tab
    • Added a way to control what badges are shown in the main DOM tree (253579@main)
    • Added Event badges for nodes that directly have event listeners in the main DOM tree (253727@main)
    • Changed the Computed panel to no longer show the Variables section when the selected element has no CSS variables (253562@main)
    • Changed the Computed panel to sort prefixed properties below non-prefixed properties (253789@main)
    • Changed the Computed panel to only show inline swatches that actually preview the value (253214@main)
  • Network Tab
    • Fixed highlighting only the initiated resources when a row is hovered (253476@main)


  • Added support for background images on ::first-line (253553@main)
  • Added right-to-left direction support for text-overflow: ellipsis (253689@main)
  • Implemented forced-colors media query (253290@main)
  • Fixed text-decoration location for superscripts when the text string contains both superscript and regular text (253406@main)
  • Fixed handling of layout and paint containment with internal ruby boxes (253354@main)
  • Fixed :has(:lang(~)) invalidation with dynamic changes (253764@main)
  • Fixed @supports to not work if “not”, “or”, or “and” isn’t follow by a space (253194@main)
  • Fixed handling of font-variant: normal and font-variant: none shorthand (253731@main)
  • Fixed behavior of cursor: auto over links (253685@main)
  • Fixed color-scheme to not propagate to the viewport background if set on the <body> (253565@main)
  • Fixed tiled layer flicker when an animation ends (253549@main)
  • Fixed the transferred min and max sizes to be constrained by defined sizes (253262@main)
  • Fixed pseudo-elements not treated as ASCII case-insensitive (253631@main)
  • Fixed the title of non-CSS stylesheets to be not preferred (253632@main)
  • Fixed input[type=search] to hide icons when appearance: textfield is set (253691@main)
  • Included aspect-ratio when calculating inline min-content size and add min-content block computation (253740@main)
  • Renamed initial value of color-scheme from auto to normal (253659@main)
  • Used align-content when calculating the static position of absolutely-positioned flexbox children (253389@main)
  • Used logical top, bottom, and height when computing the available height for an out-of-flow block (253312@main)


  • Implemented import-assertion and JSON module (253234@main)
  • Implemented Temporal.PlainDateTime to the extent of the current PlainDate implementation (Behind the --useTemporal flag) (253623@main)
  • Fixed not generating import.meta object if it is not referenced (253636@main)
  • Optimized async and await and microtask queue (253651@main)
  • Optimized Promise.all (253716@main)
  • Added support for WebAssembly GC recursion groups (253491@main)


  • Changed to not generate text runs for empty text content (253569@main)
  • Fixed text-overflow: ellipsis to change the text content to render (253607@main)
  • Fixed text-overflow: ellipsis to not affect geometries (253650@main)
  • Fixed underline thickness to align with device pixels (253182@main)
  • Stopped propagating scroll-snap style from <body> to viewport (253430@main)

Screen Capture

  • Changed to reject the getDisplayMedia prompt if the system picker times out (253260@main)


  • Included explicit !important min, max properties, and explicit width and height for full-screen elements (253790@main)
  • Fixed MediaRecorder.stop() firing an additional dataavailable event with bytes after MediaRecorder.pause() (253529@main)
  • Fixed audio pitch changes on enabling or disabling the mic capture (253673@main)
  • Fixed screen recording in a background tab working for only about a minute (253769@main)


  • Stopped applying the buffer offset twice in getBufferSubData (253175@main)


  • Implemented the Imperative Slot API (253187@main, 253198@main, 253199@main, 253202@main, 253266@main, 253320@main, 253359@main, 253365@main, 253392@main, 253396@main, 253402@main)
  • Implemented full search for text directives for Scroll to Text Fragment spec (253383@main)
  • Added Set-Cookie as a forbidden header name (253325@main)
  • Added generic media query parser and evaluator (253298@main)
  • Added support to the Permissions API for dedicated workers (253447@main)
  • Added support to the Permissions API for Service Workers and Shared Workers (253752@main)
  • Added way to get parent inline box to the inline iterator (253304@main)
  • Added box-sizing: border-box to table in the User-Agent stylesheet (253581@main)
  • Fixed blob URLs with a fragment from opaque origins cannot be loaded (253498@main)
  • Fixed ignored charset of blobs (253458@main)
  • Fixed composited canvas element to update the layer configuration after creating a WebGL context (253231@main)
  • Fixed position elements should layout relative to transformed container (253809@main)
  • Fixed Navigator.share() rejecting with the wrong exception when called multiple times (253419@main)
  • Fixed iframe srcdoc with a quirky doctype to use no-quirks mode (253326@main)
  • Fixed input[type=search] preferred content width to not include decorations (253595@main)
  • Fixed Permissions.query to return “prompt“ for opaque origins (253785@main)
  • Fixed scheduling a navigation to a Blob URL to keep the URL alive until the navigation actually occurs (253435@main)
  • Fixed forms with a disabled submit button from being posted with the Enter key (253228@main)
  • Fixed the HTML parser to ignore starting <head> tags in the “in head noscript” state (253489@main)
  • Fixed the HTML parser’s foster-parenting algorithm to not require foster parents to be elements (253504@main)
  • Fixed the HTML parser’s adoption agency algorithm to not reverse the order of nodes (253505@main)
  • Fixed an issue where data-x-2="" is not included in the dataset if it’s the only data attribute (253625@main)


  • Added support for the ARIA 1.3 property aria-description (253184@main)
  • Fixed updating accessibility objects role after a dynamic contenteditable change (253630@main)


  • Added support for Wheel input source and actions (253578@main)
  • Added support for “Get Computed Role” and “Get Computed Label” commands (253732@main)
  • Fixed automation hanging indefinitely when dismissing alerts

September 07, 2022 08:19 PM

August 31, 2022

WebKit on GitHub!

Surfin’ Safari

On June 23rd, the WebKit project froze its Subversion tree and transitioned management and interaction with our source code to git on GitHub.

WebKit project GitHub page

Why git?

git’s distributed nature makes it easy for not just multiple developers, but multiple organizations to collaborate on a single project. git’s local record of changes makes moving commits between branches or reverting changes simple and quick. git’s author and committer model does a good job representing the complex ways a large software project like WebKit writes and manages code. git’s local record of commit messages, along with git log’s ability to limit commit history to certain parts of the repository, mean large projects no longer require antiquated ChangeLog files be checked in with each commit.

In addition to git’s strengths, its ubiquity in software engineering meant that most new contributors to the WebKit project found themselves preferring to work from git-svn mirrors of the WebKit project already, so transitioning our project to exclusively git worked well with existing tools and workflows. It also means that the WebKit team will have many options for tools and services which integrate well with git.

Why GitHub?

The WebKit project is interested in contributions and feedback from developers around the world. GitHub has a very large community of developers, especially web developers, with whom the WebKit project works closely with to improve the engine that brings those developer’s creations into the hands of users around the world. We also found that GitHub’s API let us build out advanced pre-commit and post-commit automation with relatively minor modification to our existing infrastructure, and provides a modern and secure platform to review and provide feedback on new code changes.

Maintaining Order

One drawback of git is that git hashes are not naturally ordered. The WebKit team has found that the ability to easily reason about the order of commits in our repository is crucial for our zero-tolerance performance regression policy. We’ve decided to use what we’re calling “commit identifiers” in workflows that require bisection.

On the main branch, commit identifiers are a count of the number of ancestors a commit has. On a branch off of main, commit identifiers are the number of ancestors on main combined with the number of ancestors on the branch. Commit identifiers can be computed with git rev-list --count <ref> on main and git rev-list --count main..<ref> on a branch.

The WebKit team has developed a few simple tools to work with commit identifiers, most notably Tools/Scripts/git-webkit (which offers git commands compatible with identifiers) and (a simple web service for translating between different commit representations). All of our commits embed their commit identifier inside their commit message via a link. We’ve outlined in detail how commit identifiers work on the Source Control page on the GitHub wiki.

You Can Contribute!

We always welcome new contributors to the project. Get started by checking out WebKit from GitHub today! Consult our “Getting Started” documentation for information on building, testing and contributing improvements to the project. The WebKit Team is also available on Slack at #WebKit, and we’re always ready to help folks get involved with the project on the webkit-dev mailing list.

August 31, 2022 07:15 PM

August 24, 2022

Release Notes for Safari Technology Preview 152

Surfin’ Safari

Safari Technology Preview Release 152 is now available for download for macOS Monterey 12.3 or later and macOS Ventura beta. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

This release includes WebKit changes between: 252999@main…253168@main

Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

Web Inspector

  • Fixed stack traces to not include injected script host hooks (253022@main)
  • Changed to sort headers alphabetically in the Network tab (253138@main)


  • Fixed logical viewport units in font-size (253087@main)
  • Fixed MediaQueryList.matches to reflect iframe’s resize for viewport-dependent media queries (253123@main)
  • Fixed the color CSS property value being ignored on <select> elements (253074@main)
  • Fixed using the correct box-sizing when calculating block size (253064@main)
  • Made system colors respect inherited color-scheme values (253041@main)
  • Updated Container Queries to allow logical operators on the top-level without parenthesis (253035@main)


  • Fixed display: contents elements being inserted in the wrong AX tree position when they have inline renderer siblings (253038@main)


  • Fixed empty generated content to not prevent margin collapsing (253079@main)
  • Fixed clearing inline elements with a block ::before following a floated element (253076@main)
  • Prevented background propagation on <html> with any containment (253031@main)


  • Added NoIndexing miss optimization (253120@main)
  • Aligned Date.UTC to the specification (253044@main)
  • Changed to no longer ensure length validity for TypedArray iteration (253153@main)
  • Enabled Atomics object unconditionally (253137@main)
  • Enabled and renamed Array#groupBy to Array#group and Array#groupByToMap to Array#groupToMap (253101@main)
  • Implemented Symbols as WeakMap keys (253135@main)


  • Implemented Compression Streams API (253093@main)
  • Adopted Upgrade-Insecure-Request in SharedWorker handling (253167@main)
  • Changed to use EXIF orientation when creating an ImageBitmap from Blob (253004@main)
  • Deprecated window.styleMedia and removed its matchMedium() method (253168@main)
  • Fixed innerHTML and outerHTML setters to use the correct default namespace (253039@main)
  • Fixed <html> element to not have a manifest IDL attribute (253053@main)
  • Fixed decoding empty formdata with fetch (253047@main)
  • Fixed cloning a textarea to not set the initial selection at the end of the text content (253070@main)
  • Fixed XML parser to invoke custom element reactions at correct timing (253122@main)
  • Fixed loads from a dedicated worker not getting intercepted by the service worker when the calling worker script was cached (253037@main)
  • Stopped killing service workers if they don’t respond to subresource loads in a timely fashion (253037@main)

August 24, 2022 09:49 PM

August 18, 2022

Using :has() as a CSS Parent Selector and much more

Surfin’ Safari

It’s been a long-standing dream of front-end developers to have a way to apply CSS to an element based on what’s happening inside that element.

Maybe we want to apply one layout to an article element if there’s a hero image at the top, and a different layout if there is no hero image. Or maybe we want to apply different styles to a form depending on the state of one of its input fields. How about giving a sidebar one background color if there’s a certain component in that sidebar, and a different background color if that component is not present? Use cases like these have been around for a long time, and web developers have repeatedly approached the CSS Working Group, begging them to invent a “parent selector”.

Over the last twenty years, the CSS Working Group discussed the possibility many, many times. The need was clear and well understood. Defining syntax was a doable task. But figuring out how a browser engine could handle potentially very complex circular patterns, and get through the calculations fast enough seemed impossible. Early versions of a parent selector were drafted for CSS3, only to be deferred. Finally, the :has() pseudo-class was officially defined in CSS Selectors level 4. But having a web standard alone didn’t make :has() a reality. We still needed a browser team to figure out the very real performance challenge. In the meantime, computers continued to get more powerful and faster year after year.

In 2021, Igalia started advocating for :has() among browser engineering teams, prototyping their ideas and documenting their findings regarding performance. The renewed attention on :has() caught the attention of engineers who work on WebKit at Apple. We started implementing the pseudo-class, thinking through possibilities for the needed performance enhancements to make this work. We debated whether to start with a faster version with a very limited and narrow scope of what it could do, and then try to remove those limits if possible… or to start with something that had no limits, and only apply restrictions as required. We went for it, and implemented the more powerful version. We developed a number of novel :has-specific caching and filtering optimizations, and leveraged the existing advanced optimization strategies of our CSS engine. And our approach worked, proving that after a two decade wait, it is finally possible to implement such a selector with fantastic performance, even in the presence of large DOM trees and large numbers of :has() selectors.

The WebKit team shipped :has() in Safari Technology Preview 137 in December 2021, and in Safari 15.4 on March 14, 2022. Igalia did the engineering work to implement :has() in Chromium, which will ship in Chrome 105 on August 30, 2022. Presumably the other browsers built on Chromium won’t be far behind. Mozilla is currently working on the Firefox implementation.

So, let’s take a step-by-step hands-on look at what web developers can do with this desperately desired tool. It turns out, the :has() pseudo-class is not just a “parent selector”. After decades of dead-ends, this selector can do far more.

The basics of how to use :has() as a parent selector

Let’s start with the basics. Imagine we want to style a <figure> element based on the kind of content in the figure. Sometimes our figure wraps only an image.

  <img src="flowers.jpg" alt="spring flowers">

While other times there’s an image with a caption.

  <img src="dog.jpg" alt="black dog smiling in the sun">
  <figcaption>Maggie loves being outside off-leash.</figcaption>

Now let’s apply some styles to the figure that will only apply if there is a figcaption inside the figure.

figure:has(figcaption) {
  background: white;
  padding: 0.6rem;

This selector means what it says — any figure element that has a figcaption inside will be selected.

Here’s the demo, if you’d like to alter the code and see what happens. Be sure to use a browser that supports :has() — as of today, that’s Safari.

See the Pen
:has() demo — Figure variations
by Jen Simmons (@jensimmons)
on CodePen.

In this demo, I also target any figure that contains a pre element by using figure:has(pre).

figure:has(pre) { 
  background: rgb(252, 232, 255);
  border: 3px solid white;
  padding: 1rem;

And I use a Selector Feature Query to hide a reminder about browser support whenever the current browser supports :has().

@supports selector(:has(img)) {
  small {
    display: none;

The @supports selector() at-rule is itself very well supported. It can be incredibly useful anytime you want to use a feature query to test for browser support of a particular selector.

And finally, in this first demo, I also write a complex selector using the :not() pseudo-class. I want to apply display: flex to the figure — but only if an image is the sole content. Flexbox makes the image stretch to fill all available space.

I use a selector to target any figure that does not have any element that is not an image. If the figure has a figcaption, pre, p, or an h1 — or any element at all besides img — then the selector doesn’t apply.

figure:not(:has(:not(img))) {
  display: flex;

:has() is a powerful thing.

A practical example using :has() with CSS Grid

Let’s look at a second demo where I’ve used :has() as a parent selector to easily solve a very practical need.

I have several article teaser cards laid out using CSS Grid. Some cards contain only headlines and text, while others also have an image. I want the cards with images to take up more space on the grid than those without images.

I don’t want to have to do extra work to get my content management system to apply a class or to use JavaScript for layout. I just want to write a simple selector in CSS that will tell the browser to make any teaser card with an image to take up two rows and two columns in the grid.

The :has() pseudo-class makes this simple:

article:has(img) {
  grid-column: span 2;
  grid-row: span 2;

See the Pen
:has() demo — teaser cards
by Jen Simmons (@jensimmons)
on CodePen.

These first two demos use simple element selectors from the early days of CSS, but all of the selectors can be combined with :has(), including the class selector, the ID selector, the attribute selector — and powerful combinators.

Using :has() with the child combinator

First, a quick review of the difference between the descendant combinator and the child combinator (>).

The descendant combinator has been around since the very beginning of CSS. It’s the fancy name for when we put a space between two simple selectors. Like this:

a img { ... }

This targets all img elements that are contained within an a element, no matter how far apart the a and the img are in the HTML DOM tree.

    <img src="photo.jpg" alt="don't forget alt text" width="200" height="100">

Child combinator is the name for when we put an > between two selectors — which tells the browser to target anything that matches the second selector, but only when the second selector is a direct child of the first.

a > img { ... }

For example, this selector targets all img elements wrapped by an a element, but only when the img is immediately after the a in the HTML.

  <img src="photo.jpg" alt="don't forget alt text" width="200" height="100">

With that in mind, let’s consider the difference between the following two examples. Both select the a element, rather than the img, since we are using :has().

a:has(img) { ... }
a:has(> img) { ... }

The first selects any a element with an img inside — any place in the HTML structure. While the second selects an element only if the img is a direct child of the a.

Both can be useful; they accomplish different things.

See the Pen
:has() — descendant combinator vs child combinator
by Jen Simmons (@jensimmons)
on CodePen.

There are two additional types of combinators — both are siblings. And it’s through these that :has() becomes more than a parent selector.

Using :has() with sibling combinators

Let’s review the two selectors with sibling relationships. There’s the next-sibling combinator (+) and the subsequent-sibling combinator (~).

The next-sibling combinator (+) selects only the paragraphs that come directly after an h2 element.

h2 + p
<p>Paragraph that is selected by `h2 + p`, because it's directly after `h2`.</p>

The subsequent-sibling combinator (~) selects all paragraphs that come after an h2 element. They must be siblings, but there can be any number of other HTML elements in between.

h2 ~ p
<h3>Something else</h3>
<p>Paragraph that is selected by `h2 ~ p`.</p>
<p>This paragraph is also selected.</p>

Note that both h2 + p and h2 ~ p select the paragraph elements, and not the h2 headlines. Like other selectors (think of a img), it’s the last element listed that is targeted by the selector. But what if we want to target the h2? We can use sibling combinators with :has().

How often have you wanted to adjust the margins on a headline based on the element following it? Now it’s easy. This code allows us to select any h2 with a p immediately after it.

h2:has(+ p) { margin-bottom: 0; }


What if we want to do this for all six headline elements, without writing out six copies of the selector. We can use :is to simplify our code.

:is(h1, h2, h3, h4, h5, h6):has(+ p) { margin-bottom: 0; }

Or what if we want to write this code for more elements than just paragrapahs? Let’s eliminate the bottom margin of all headlines whenever they are followed by paragraphs, captions, code examples and lists.

:is(h1, h2, h3, h4, h5, h6):has(+ :is(p, figcaption, pre, dl, ul, ol)) { margin-bottom: 0; }

Combining :has() with descendant combinators, child combinators (>), next-sibling combinators (+), and subsequent-sibling combinators (~) opens up a world of possibilities. But oh, this is still just the beginning.

Styling form states without JS

There are a lot of fantastic pseudo-classes that can be used inside has:(). In fact, it revolutionizes what pseudo-classes can do. Previously, pseudo-classes were only used for styling an element based on a special state — or styling one of its children. Now, pseudo-classes can be used to capture state, without JavaScript, and style anything in the DOM based on that state.

Form input fields provide a powerful way to capture such a state. Form-specific pseudo-classes include :autofill, :enabled, :disabled, :read-only, :read-write, :placeholder-shown, :default, :checked, :indeterminate, :valid, :invalid, :in-range, :out-of-range, :required and :optional.

Let’s solve one of the use cases I described in the introduction — the long-standing need to style a form label based on the state of the input field. Let’s start with a basic form.

    <label for="name">Name</label> 
    <input type="text" id="name">
    <label for="site">Website</label> 
    <input type="url" id="site">
    <label for="email">Email</label>
    <input type="email" id="email">

I’d like to apply a background to the whole form whenever one of the fields is in focus.

form:has(:focus-visible) { 
  background: antiquewhite;

Now I could have used form:focus-within instead, but it would behave like form:has(:focus). The :focus pseudo-class always applies CSS whenever a field is in focus. The :focus-visible pseudo-class provides a reliable way to style a focus indicator only when the browser would draw one natively, using the same complex heuristics the browser uses to determine whether or not to apply a focus-ring.

Now, let’s imagine I want to style the other fields, the ones not in focus — changing their label text color and the input border color. Before :has(), this required JavaScript. Now we can use this CSS.

form:has(:focus-visible) div:has(input:not(:focus-visible)) label {
  color: peru;
form:has(:focus-visible) div:has(input:not(:focus-visible)) input {
  border: 2px solid peru;

What does that selector say? If one of the controls inside this form has focus, and the input element for this particular form control does not have focus, then change the color of this label’s text to peru. And change the border of the input field to be 2px solid peru.

You can see this code in action in the following demo by clicking inside one of the text fields. The background of the form changes, as I described earlier. And the label and input border colors of the fields that are not in focus also change.

See the Pen
:has() demo: Forms
by Jen Simmons (@jensimmons)
on CodePen.

In this same demo, I would also like to improve the warning to the user when there’s an error in how they filled out the form. For years, we’ve been able to easily put a red box around an invalid input with this CSS.

input:invalid {
  outline: 4px solid red;
  border: 2px solid red;

Now with :has(), we can turn the label text red as well:

div:has(input:invalid) label {
  color: red;

You can see the result by typing something in the website or email field that’s not a fully-formed URL or email address. Both are invalid, and so both will trigger a red border and red label, with an “X”.

Dark mode toggle with no JS

And last, in this same demo I’m using a checkbox to allow the user to toggle between a light and dark theme.

body:has(input[type="checkbox"]:checked) {
  background: blue;
  --primary-color: white;
body:has(input[type="checkbox"]:checked) form { 
  border: 4px solid white;
body:has(input[type="checkbox"]:checked) form:has(:focus-visible) {
  background: navy;
body:has(input[type="checkbox"]:checked) input:focus-visible {
  outline: 4px solid lightsalmon;

I’ve styled the dark mode checkbox using custom styles, but it does still look like a checkbox. With more complex styles, I could create a toggle in CSS.

In a similar fashion, I could use a select menu to provide a user with multiple themes for my site.

body:has(option[value="pony"]:checked) {
  --font-family: cursive;
  --text-color: #b10267;
  --body-background: #ee458e;
  --main-background: #f4b6d2;

See the Pen
:has() Demo #5 — Theme picker via Select
by Jen Simmons (@jensimmons)
on CodePen.

Any time there’s an opportunity to use CSS instead of JavaScript, I’ll take it. This results in a faster experience and a more robust website. JavaScript can do amazing things, and we should use it when it’s the right tool for the job. But if we can accomplish the same result in HTML and CSS alone, that’s even better.

And more

Looking through other pseudo-classes, there are so many that can be combined with :has(). Imagine the possibilities with :nth-child, :nth-last-child, :first-child, :last-child, :only-child, :nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type. The brand new :modal pseudo-class is triggered when a dialog is in the open state. With :has(:modal) you can style anything in the DOM based on whether the dialog is open or closed.

However, not every pseudo-class is currently supported inside :has() in every browser, so do try out your code in multiple browsers. Currently the dynamic media pseudo-classes don’t work — like :playing, :paused, :muted, etc. They very well may work in the future, so if you are reading this in the future, test them out! Also, form invalidation support is currently missing in certain specific situations, so dynamic state changes to those pseudo-classes may not update with :has().

Safari 16 will add support for :has(:target) opening up interesting possibilities for writing code that looks at the current URL for a fragment that matches the ID of a specific element. For example, if a user clicks on a table of contents at the top of a document, and jumps down to the section of the page matching that link, :target provides a way to style that content uniquely, based on the fact the user clicked the link to get there. And :has() opens up what such styling can do.

Something to note — the CSS Working Group resolved to disallow all existing pseudo-elements inside of :has(). For example, article:has(p::first-line) and ol:has(li::marker) won’t work. Same with ::before and ::after.

The :has() revolution

This feels like a revolution in how we will write CSS selectors, opening up a world of possibilities previously either impossible or often not worth the effort. It feels like while we might recognize immediately how useful :has() will be, we also have no idea what is truly possible. Over the next several years, people who make demos and dive deep into what CSS can do will come up with amazing ideas, stretching :has() to its limits.

Michelle Barker created a fantastic demo that triggers the animation of Grid track sizes through the use of :has() and hover states. Read more about it in her blog post. Support for animated grid tracks will ship in Safari 16. You can try out this demo today in Safari Technology Preview or Safari 16 beta.

The hardest part of :has() will be opening our minds to its possibilities. We’ve become so used to the limits imposed on us by not having a parent selector. Now, we have to break those habits.

That’s all the more reason to use vanilla CSS, and not limit yourself to the classes defined in a framework. By writing your own CSS, custom for your project, you can fully leverage all the powerful abilities of today’s browsers.

What will you use :has() for? Last December, I asked on Twitter what use cases folks might have for :has(), and got lots of replies with incredible ideas. I can’t wait to see yours.

August 18, 2022 03:30 PM

August 11, 2022

Release Notes for Safari Technology Preview 151

Surfin’ Safari

Safari Technology Preview Release 151 is now available for download for macOS Monterey 12.3 or later and macOS Ventura beta. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

This release includes WebKit changes between:

Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

Web Inspector

  • Elements Tab
    • Changed to grayscale nodes in the DOM tree that are not rendered (252653@main)
    • Rearranged Gradient Editor to be able to show the color component input fields (252971@main)
  • Sources Tab
    • Added capturing of async stack traces for queueMicrotask (252543@main)
    • Expanded all existing sync stack traces to also show related async stack traces (252630@main)
    • Excluded internal functions from all stack traces (252914@main)
  • Network Tab
    • Changed the Initiator column to be shown by default (252435@main)
    • Added a Path column (252827@main)
    • Added the ability for the shift key to highlight resources initiated by the hovered resource (252489@main)
    • Added an experimental feature to limit the bytes per second download speed of network tasks (252589@main)


  • Added support for “Get Element Shadow Root”, “Find Element From Shadow Root”, and “Find Elements From Shadow Root” commands


  • Enabled support for the color-mix() function (252716@main)
  • Enabled support for specifying the interpolation color space in CSS gradients (252716@main)
  • Added support for the scan media query (252717@main)
  • Added a basic implementation of :dir (252737@main)
  • Disallowed the content property on video element (252574@main)
  • Fixed not applying aspect-ratio if a grid item percentage width is resolvable (252819@main)
  • Fixed ensuring a containing block of a block box with height: fill-available is also fill-available is considered resolvable (252470@main)
  • Fixed table border radius to apply when the border color is transparent or semitransparent (252741@main)
  • Fixed containing blocks with non visible overflow to correctly clip fixed positioned descendants (252721@main)
  • Fixed visibility: collapse to correctly apply to Flexbox (252997@main)


  • Added dark mode support for plain text documents (252673@main)
  • Added a check for WebGL Extensions in getIndexedParameter() and getSamplerParameter() (252895@main)
  • Fixed <bdi> and <input> elements’ directionality when :dir attribute is used with auto or an invalid value (252779@main)
  • Changed to use the device pixel ratio scaled backing stores for unscaled bitmap content if the page is downscaling (252855@main)
  • Fixed SVG textPath rendering when a text element is referenced by a use element (252547@main)


  • Added support for legacy VP8 and VP9 codec names (252535@main)
  • Added missing default CSS style for text and background colors for WebVTT (252464@main)
  • Changed to not interrupt due to invisible autoplay if the HTMLMediaElement is paused (252735@main)
  • Fixed WebRTC calls without microphone capture becoming permanently muted by AudioSession interruptions like Siri (252907@main)
  • Fixed video-as-img losing its alpha channel (252836@main)
  • Fixed muting the microphone capture if starting the capture fails due to a low priority error (252642@main)
  • Fixed not restarting application-muted microphone tracks in case the audio unit gets unsuspended (252905@main)
  • Fixed RTCDtlsTransport to close when its peer connection gets closed (252948@main)
  • Fixed Canvas.getContext('2d').drawImage on a camera video stream when the tab is backgrounded (252738@main)


  • Fixed Temporal.Duration#toString to use BigInt#toString for values beyond MAX_SAFE_INTEGER (Behind the --useTemporal flag) (252935@main)
  • Improved TypedArray.from performance (252976@main)
  • Improved TypedArray#indexOf and TypedArray#includes performance (252945@main, 252544@main)
  • Removed ordering check for Intl.NumberFormat, Intl.DateTimeFormat, and Intl.PluralRules range functions (252911@main)
  • Improved TypedArray#toSorted, TypedArray#toReversed, TypedArray#with and TypedArray#subarray performance (252911@main)
  • Improved TypedArray#slice performance (252847@main)
  • Removed TypedArray#toSpliced since it is dropped from the proposal (252664@main)
  • Fixed that generic iterator protocol function is invisible to Function’s caller getter (252578@main)


  • Aligned StorageEvent.initStorageEvent() with the HTML specification (252521@main)
  • Aligned XHR aborting with the specification (252611@main)
  • Changed to not perform smooth keyboard scrolling on containers with scroll snap (252663@main)
  • Changed to maintain the original Content-Type header on 303 HTTP redirect (252713@main)
  • Checked for strict-dynamic when checking host sources for CSP (252465@main)
  • Enabled lazy iframe loading by default (252848@main)
  • Exposed the sheet property on SVGStyleElement (252491@main)
  • Fixed alternate stylesheets not present in document.styleSheets (252781@main)
  • Fixed CSS.supports returning false for custom properties (252987@main)
  • Fixed a display: none HTMLMediaElement with a camera track as the source leaking video frames (252871@main)
  • Fixed getComputedStyle(img).height to return a string of a rounded int not a float (252583@main)
  • Fixed a visible line character in the space between paragraphs when using a double space while composing an email with Gmail in Safari (252683@main)
  • Fixed XHR error events to return zeros for loaded and total (252743@main)
  • Fixed password authentication in XHR (252696@main)
  • Fixed canvas drawImage to clip source image when the source rectangle is outside of the source image (252731@main)
  • Fixed dynamically inserted stylesheets to be accessible via document.styleSheets (252816@main)
  • Fixed link elements to be able to fire more than one load or error event (252943@main)
  • Fixed parsing link, vlink, and alink content attributes on the <body> element (252992@main)
  • Fixed SVGDocument getElementById returning null when an SVG element is disconnected from documents (252478@main)
  • Fixed when a WebLock gets aborted via a Signal to reject its promise with the AbortSignal‘s reason (252503@main)
  • Fixed Service Workers in browser extensions to be able to load resources over a custom protocol (252585@main)
  • Fixed WorkerGlobalScope.isSecureContext to be based on the owner’s top URL, not the owner’s URL (252913@main)
  • Aligned CSS.supports with the specification to return false for descriptors (252720@main)
  • Implemented fingerprinting countermeasures for querying the permission state of the Notifications API (252969@main)
  • Stopped allowing trailing junk when parsing retry delay for EventSource (252885@main)
  • Fixed parsing of deeply nested phrasing content (252979@main)
  • Fixed removing srcdoc content attribute does not unload the document in iframe (252991@main)

Web Components

  • Updated shadow DOM and dialog element focusing to the latest spec (252959@main)
  • Updated delegatesFocus to match the latest spec (252901@main)
  • Fixed custom element reaction callback timing when modifying ARIA attributes (252552@main)
  • Fixed custom element reaction callback timing when using WebAnimation.prototype.commitStyles (252553@main)


  • Exposed suggestion, insertion, deletion roles and attributes (252516@main)
  • Enabled ARIA reflection attributes that refer to elements (252590@main)

Lockdown Mode

Web Extensions

  • Added API for storing data in-memory that is not written to disk

August 11, 2022 10:29 PM

August 02, 2022

Speedometer 2.1

Surfin’ Safari

We are announcing an update to the Speedometer benchmark. This is a minor version bump fixing the benchmark harness to improve the accuracy and stability of how we measure the speed of the rendering engine.

setTimeout nesting level and throttling

Speedometer was designed to include asynchronous work in its work time measurement when computing a score. This is needed because some browser engines use an optimization strategy of deferring some work to reduce the run time of synchronous operations. To measure the time used for asynchronous work, the Speedometer test harness was using setTimeout to compute the end time after the asynchronous work is done.

However, using setTimeout inside the test harness for each subtest piles up the setTimeout nesting level. According to the specification, this triggers the insertion of an artificial 4ms throttle between each setTimeout callback for energy efficiency, and results in the measured work time including this throttling. This is not ideal because (1) we reach a deep nesting level due to how the test harness is constructed, not due to test content, and (2) the intended purpose of using setTimeout in the harness is to measure the time of deferred asynchronous work, not artificial throttling.

This update inserts window.requestAnimationFrame for each test run. This resets the setTimeout nesting level added by the test harness and other subtests, and increases the accuracy and stability of the work time measurement by isolating each subtest more. We observed a 3-5% improvement in stable Safari, Chrome, and Firefox.


Speedometer 2.1 improves the accuracy and stability of the score measurement by ensuring that it captures the work time of asynchronous work without inducing web engine timer throttling.

August 02, 2022 04:00 PM

July 29, 2022

Understanding Garbage Collection in JavaScriptCore From Scratch

Surfin’ Safari

JavaScript relies on garbage collection (GC) to reclaim memory. In this post, we will dig into JSC’s garbage collection system.

Before we start, let me briefly introduce myself. I am Haoran Xu, a PhD student at Stanford University. While I have not yet contributed a lot to JSC, I found JSC a treasure of elegant compiler designs and efficient implementations, and my research is exploring ways to transfer JSC’s design to support other programming languages like Lua at a low engineering cost. This post was initially posted on my blog — great thanks to the WebKit project for cross-posting it on their official blog!

Filip Pizlo’s blog post on GC is great at explaining the novelties of JSC’s GC, and also positions it within the context of various GC schemes in academia and industry. However, as someone with little GC background, I felt the blog alone insufficient for me to get a solid understanding of the algorithm and the motivation behind the design. Through digging into the code, and with some great help from Saam Barati, one of JSC’s lead developers, I wrote up this blog post in the hope that it can help more people understand this beautiful design.

The garbage collector in JSC is non-compacting, generational and mostly[1]concurrent. On top of being concurrent, JSC’s GC heavily employs lock-free programming for better performance.

As you can imagine, JSC’s GC design is quite complex. Instead of diving into the complex invariants and protocols, we will start with a simple design, and improve it step by step to converge at JSC’s design. This way, we not only understand why JSC’s design works, but also how JSC’s design was constructed over time.

But first of all, let’s get into some background.

Memory Allocation in JSC

Memory allocators and GCs are tightly coupled by nature – the allocator allocates memory to be reclaimed by the GC, and the GC frees memory to be reused by the allocator. In this section, we will briefly introduce JSC’s memory allocators.

At the core of the memory allocation scheme in JSC is the data structure BlockDirectory[2]. It implements a fixed-sized allocator, that is, an allocator that only allocates memory chunks of some fixed size S. The allocator keeps tracks of a list of fixed-sized (in current code, 16KB) memory pages (“blocks”) it owns, and a free list. Each block is divided into cells of size S, and has a footer at its end[3], which contains metadata needed for the GC and allocation, e.g., which cells are free. By aggregating and sharing metadata at the footer, it both saves memory and improves performance of related operations: we will go into the details later in this post.

When a BlockDirectory needs to make an allocation, it tries to allocate from its free list. If the free list is empty, it tries to iterate through the blocks it owns[4], to see if it can find a block containing free cells (which are marked free by GC). If yes, it scans the block footer metadata to find out all the free cells[5] in this block, and put into the free list. Otherwise, it allocates a new block from malloc[6]. Note that this implies a BlockDirectory’s free list only contains cells in one block: this is called m_currentBlock in the code, and we will revisit this later.

BlockDirectory is used as the building block to build the memory allocators in JSC. JSC employs three kinds of allocators:

  1. CompleteSubspace: this is a segregated allocator responsible for allocating small objects (max size about 8KB). Specifically, there is a pre-defined list of exponentially-growing size-classes[7], and one BlockDirectory is used to handle allocation for each size class. So to allocate an object, you find the smallest size class large enough to hold the object, and allocate from the directory for that size class.
  2. PreciseAllocation: this is used to handle large allocations that cannot be handled by the CompleteSubspace allocator[8]. It simply relies on the standard (malloc-like) memory allocator, though in JSC a custom malloc implementation called libpas is used. The downside is that since a PreciseAllocation is created on a per-object basis, the GC cannot aggregate and share metadata information of multiple objects together to save memory and improve performance (as MarkedBlock’s block footer did). Therefore, every PreciseAllocation comes with a whopping overhead of a 96-byte GC header to store the various metadata information needed for GC for this object (though this overhead is justified since each allocation is already at least 8KB).
  3. IsoSubspace: each IsoSubspace is used to allocate objects of a fixed type with a fixed size. So each IsoSubspace simply holds a BlockDirectory to do allocation (though JSC also has an optimization for small IsoSubspace by making them backed by PreciseAllocation[9]). This is a security hardening feature that makes use-after-free-based attacks harder[10].

IsoSubspace is mostly a simplified CompleteSubspace, so we will ignore it for the purpose of this post. CompleteSubspace is the one that handles the common case: small allocations, and PreciseAllocation is mostly the rare slow path for large allocations.

Generational GC Basics

In JSC’s generational GC model, the heap consists of a small “new space” (eden), holding the newly allocated objects, and a large “old space” holding the older objects that have survived one GC cycle. Each GC cycle is either an eden GC or a full GC. New objects are allocated in the eden. When the eden is full, an eden GC is invoked to garbage-collect the unreachable objects in eden. All the surviving objects in eden are then considered to be in the old space[11]. To reclaim objects in the old space, a full GC is needed.

The effectiveness of the above scheme relies on the so-called “generational hypothesis”:

  1. Most objects collected by the GC are young objects (died when they are still in eden), so an eden GC (which only collects the eden) is sufficient to reclaim most newly allocated memory.
  • Pointers from old space to eden is much rarer than pointers from eden to old space or pointers from eden to eden, so an eden GC’s runtime is approximately linear to the size of the eden, as it only needs to start from a small subset of the old space. This implies that the cost of GC can be amortized by the cost of allocation.

  • Inlined vs. Outlined Metadata: Why?

    Practically every GC scheme uses some kind of metadata to track which objects are alive. In this section, we will explain how the GC metadata is stored in JSC, and the motivation behind its design.

    In JSC, every object managed by the GC carries the following metadata:

    1. Every object managed by the GC inherits the JSCell class, which contains a 1-byte member cellState. This cellState is a color marker with two colors: white and black[12].
  • Every object also has two out-of-object metadata bits: isNew[13] and isMarked. For objects allocated by PreciseAllocation, the bits reside in the GC header. For objects allocated by CompleteSubspace, the bits reside in the block footer.

  • This may seem odd at first glance since isNew and isMarked could have been stored in the unused bits of cellState. However, this is intentional.

    The inlined metadata cellState is easy to access for the mutator thread (the thread executing JavaScript code), since it is just a field in the object. However, it has bad memory locality for the GC and allocators, which need to quickly traverse through all the metadata of all objects in some block owned by CompleteSubspace (which is the common case). Outlined metadata have the opposite performance characteristics: they are more expensive to access for the mutator thread, but since they are aggregated into bitvectors and stored in the block footer of each block, GC and allocators can traverse them really fast.

    So JSC keeps both inlined and outlined metadata to get the better of both worlds: the mutator thread’s fast path will only concern the inlined cellState, while the GC and allocator logic can also take advantage of the memory locality of the outlined bits isNew and isMarked.

    Of course, the cost of this is a more complex design… so we have to unfold it bit by bit.

    A Really Naive Stop-the-World Generational GC

    Let’s start with a really naive design just to understand what is needed. We will design a generational, but stop-the-world (i.e. not incremental nor concurrent) GC, with no performance optimizations at all. In this design, the mutator side transfers control to the GC subsystem at a “safe point”[14] to start a GC cycle (eden or full). The GC subsystem performs the GC cycle from the beginning to the end (as a result, the application cannot run during this potentially long period, thus “stop-the-world”), and then transfer control back to the mutator side.

    For this purpose, let’s temporarily forget about CompleteSubspace: it is an optimized version of PrecisionAllocation for small allocations, and while it is an important optimization, it’s easier to understand the GC algorithm without it.

    It turns out that in this design, all we need is one isMarked bit. The isMarked bit will indicate if the object is reachable at the end of the last GC cycle (and consequently, is in the old space, since any object that survived a GC cycle is in old space). All objects are born with isMarked = false.

    The GC will use a breadth-first search to scan and mark objects. For full GC, we want to reset all isMarked bits to false at the beginning, and do a BFS to scan and mark all objects reachable from GC roots. Then all the unmarked objects are known to be dead. For an eden GC, we only want to scan the eden space. Fortunately, all objects in the old space are already marked at the end of the previous GC cycle, so they are naturally ignored by the BFS, so we can simply reuse the same BFS algorithm in full GC. In pseudo-code:

    Eden GC preparation phase: no work is needed.

    Full GC preparation phase[15]:

    for (JSCell* obj : heap)
        obj->isMarked = false;

    Eden/Full GC marking phase:

    while (!queue.empty()) {
        JSCell* obj = queue.pop();
        obj->ForEachChild([&](JSCell* child) {
            if (!child->isMarked) {
                child->isMarked = true;

    Eden/Full GC collection phase:

    // One can easily imagine an optimization to make eden collection
    // traverse only the eden space. We ignore it for simplicity.
    for (JSCell* obj : heap) {
        if (!obj->isMarked)

    But where does the scan start, so that we can scan through every reachable object? For full GC, the answer is clear: we just start the scan from all GC roots[16]. However, for an eden GC, in order to reliably scan through all reachable objects, the situation is slightly more complex:

    1. Of course, we still need to push the GC roots to the initial queue.
  • If an object in the old space contains a pointer to an object in eden, we need to put the old space object to the initial queue[17].

  • The invariant for the second case is maintained on the mutator side. Specifically, whenever one writes a pointer slot of some object A in the heap to point to another object B, one needs to check if A.isMarked is true and B.isMarked is false. If so, one needs to put A into a “remembered set”. An eden GC must treat the objects in the remembered set as if they were GC roots. This is called a WriteBarrier. In pseudo-code:

    // Executed after writing a pointer to 'dst' into a field of 'obj'
    if (obj->isMarked && !dst->isMarked)

    Getting Incremental

    A stop-the-world GC isn’t optimal for production use. A GC cycle (especially a full GC cycle) can take a long time. Since the mutator (application logic) cannot run during the stop-the-world period, the application would appear irresponsive to the user, which can be a very bad user experience for long pauses.

    A natural way to shorten this irresponsive period is to run GC incrementally: at safe points, the mutator transfers control to the GC. The GC only runs for a short time, doing a portion of the work for the current GC cycle (eden or full), then return control to the mutator. This way, each GC cycle is split into many small steps, so the irresponsive periods are less noticeable to the user.

    Incremental GC poses a few new challenges to the GC scheme.

    The first challenge is the extra interference between the GC and the mutator: the mutator, namely the allocator and the WriteBarrier, must be prepared to see states arisen from a partially-completed GC cycle. And the GC side must correctly mark all reachable objects despite changes made by the mutator side in between.

    Specifically, our full GC must change: imagine that the full GC scanned some object o and handed back control to mutator, then the mutator changed a field of o to point to some other object dst. The object dst must not be missed from scanning. Fortunately, in such a case o will be isMarked and dst will be !isMarked (if dst has isMarked then it has been scanned, so there’s nothing to worry about), so o will be put into the remembered set.

    Therefore, for a full GC to function correctly in the incremental GC scheme, it must consider the remembered set as a GC root as well, just like the eden GC.

    The other parts of the algorithm as of now can remain unchanged (we leave the proof of correctness as an exercise for the reader). Nevertheless, “what happens if a GC cycle is run partially?” is something that we must keep in mind as we add more optimizations.

    The second challenge is that the mutator side can repeatedly put an old space object into the remembered set, and result in redundant work for the GC: for example, the GC popped some object o in the remembered set, traversed from it, and handed over control to mutator. The mutator modified o again, putting it back to the remembered set. If this happens too often, the incremental GC could do a lot more work than a stop-the-world GC.

    The situation will get even worse once we make our GC concurrent: in a concurrent GC, since the GC is no longer stealing CPU time from the mutator, the mutator gets higher throughput, thus will add even more objects into the remembered set. In fact, JSC observed up to 5x higher memory consumption without any mitigation. Therefore, two techniques are employed to mitigate this issue.

    The first and obvious mitigation is to have the GC scan the remembered set last: only when the queue has otherwise been empty do we start popping from the remembered set. The second mitigation employed by JSC is a technique called Space-Time Scheduler. In short, if it observes that the mutator was allocating too fast, the mutator would get decreasingly less time quota to run so the GC can catch up (and in the extreme case, the mutator would get zero time quota to run, so it falls back to the stop-the-world approach). Filip Pizlo’s blog post has explained it very clearly, so feel free to take a look if you are interested.

    Anyways, let’s update the pseudo-code for the eden/full GC marking phase:

    while (!queue.empty() || !rmbSet.empty()) {
        // Both eden GC and full GC needs to consider the remembered set
        // Prioritize popping from queue, pop remembered set last
        JSCell* obj = !queue.empty() ? queue.pop() : rmbSet.pop();
        obj->ForEachChild([&](JSCell* child) {
            if (!child->isMarked) {
                child->isMarked = true;

    Incorporate in CompleteSubspace

    It’s time to get our CompleteSubspace allocator back so we don’t have to suffer the huge per-object GC header overhead incurred by PreciseAllocation.

    For PreciseAllocation, the actual memory management work is done by malloc: when the mutator wants to allocate an object, it just mallocs it, and when the GC discovers a dead object, it just frees it.

    CompleteSubspace introduces another complexity, as it only allocates/deallocates memory from malloc at 16KB-block level, and does memory management itself to divide the blocks into cells that it serves to the application. Therefore, it has to track whether each of its cells is available for allocation. The mutator allocates from the available cells, and the GC marks dead cells as available for allocation again.

    The isMarked bit is not enough for the CompleteSubspace allocator to determine if a cell contains a live object or not: newly allocated objects have isMarked = false but are clearly live objects. Therefore, we need another bit.

    In fact, there are other good reasons that we need to support checking if a cell contains a live object or not. A canonical example is the conservative stack scanning: JSC does not precisely understand the layout of the stack, so it needs to treat everything on the stack that could be pointers and pointing to live objects as a GC root, and this involves checking if a heap pointer points to a live object or not.

    One can easily imagine some kind of isLive bit that is true for all live objects, which is only flipped to false by the GC when the object is dead. However, JSC employed a slightly different scheme, which is needed to facilitate optimizations that we will mention later.

    As you have seen earlier, the bit used by JSC is called isNew.

    However, keep in mind: you should not think of isNew as a bit that tells you anything related to its name, or indicates anything by itself. You should think of it as a helper bit, which sole purpose is that, when working together with isMarked, they tell you if a cell contains a live object or not. This thinking mode will be more important in the next section when we introduce logical versioning.

    The core invariant around isNew and isMarked is:

    1. At any moment, an object is dead iff its isNew = false and isMarked = false.

    If we were a stop-the-world GC, then to maintain this invariant, we only need the following:

    1. When an object is born, it has isNew = true and isMarked = false.
  • At the end of each eden or full GC cycle, we set isNew of all objects to false.

  • Then, all newly-allocated objects are live because its isNew is true. At the end of each GC cycle, an object is live iff its isMarked is true, so after we set isNew to false (due to rule 2), the invariant on what is a dead object is maintained, as desired.

    However, in an incremental GC, since the state of a partially-run GC cycle can be exposed to mutator, we need to ensure that the invariant holds in this case as well.

    Specifically, in a full GC, we reset all isMarked to false at the beginning. Then, during a partially-run GC cycle, the mutator may see a live object with both isMarked = false (because it has not been marked by the GC yet), and isNew = false (because it has survived one prior GC cycle). This violates our invariant.

    To fix this, at the beginning of a full GC, we additionally do isNew |= isMarked before clearing isMarked. Now, during the whole full GC cycle, all live objects must have isNew = true[18], so our invariant is maintained. At the end of the cycle, all isNew bits are cleared, and as a result, all the unmarked objects become dead, so our invariant is still maintained as desired. So let’s update our pseudo-code:

    Eden GC preparation phase: no work is needed.

    Full GC preparation phase:

    // Do 'isNew |= isMarked, isMarked = false' for all
    // PreciseAllocation and all cells in CompleteSubspace
    for (PreciseAllocation* pa : allPreciseAllocations) {
        pa->isNew |= pa->isMarked;
        pa->isMarked = false;
    for (BlockFooter* block : allCompleteSubspaceBlocks) {
        for (size_t cellId = 0; cellId < block->numCells; cellId++) {
            block->isNew[cellId] |= block->isMarked[cellId];
            block->isMarked[cellId] = false;

    Eden/Full GC collection phase:

    // Update 'isNew = false' for CompleteSubspace cells
    for (BlockFooter* block : allCompleteSubspaceBlocks) {
        for (size_t cellId = 0; cellId < block->numCells; cellId++) {
            block->isNew[cellId] = false;
    // For PreciseAllocation, in addition to updating 'isNew = false',
    // we also need to free the dead objects
    for (PreciseAllocation* pa : allPreciseAllocations) {
        pa->isNew = false;
        if (!pa->isMarked)

    In CompleteSubspace allocator, to check if a cell in a block contains a live object (if not, then the cell is available for allocation):

    bool cellContainsLiveObject(BlockFooter* block, size_t cellId) {
        return block->isMarked[cellId] || block->isNew[cellId];

    Logical Versioning: Do Not Sweep!

    We are doing a lot of work at the beginning of a full GC cycle and at the end of any GC cycle, since we have to iterate through all the blocks in CompleteSubspace and update their isMarked and isNew bits. Despite that the bits in one block are clustered into bitvectors thus have good memory locality, this could still be an expensive operation, especially after we have a concurrent GC (as this stage cannot be made concurrent). So we want something better.

    The optimization JSC employs is logical versioning. Instead of physically clearing all bits in all blocks for every GC cycle, we only bump a global “logical version”, indicating that all the bits are logically cleared (or updated). Only when we actually need to mark a cell in a block during the marking phase do we then physically clear (or update) the bitvectors in this block.

    You may ask: why bother with logical versioning, if in the future we still have to update the bitvectors physically anyway? There are two good reasons:

    1. If all cells in a block are dead (either died out during this GC cycle[19], or already dead before this GC cycle), then we will never mark anything in the block, so logical versioning enabled us to avoid the work altogether. This also implies that at the end of each GC cycle, it’s unnecessary to figure out which blocks become completely empty, as logical versioning makes sure that these empty blocks will not cause overhead to future GC cycles.
  • The marking phase can be done concurrently with multiple threads and while the mutator thread is running (our scheme isn’t concurrent now, but we will do it soon), while the preparation / collection phase must be performed with the mutator stopped. Therefore, shifting the work to the marking phase reduces GC latency in a concurrent setting.

  • There are two global version number g_markVersion and g_newVersion[20]. Each block footer also stores its local version number l_markVersion and l_newVersion.

    Let’s start with the easier case: the logical versioning for the isNew bit.

    If you revisit the pseudo-code above, in GC there is only one place where we write isNew: at the end of each GC cycle, we set all the isNew bits to false. Therefore, we simply bump g_newVersion there instead. A local version l_newVersion smaller than g_newVersion means that all the isNew bits in this block have been logically cleared to false.

    When the CompleteSubspace allocator allocates a new object, it needs to start with isNew = true. One can clearly do this directly, but JSC did it in a trickier way that involves a block-level bit named allocated for slightly better performance. This is not too interesting, so I deferred it to the end of the post, and our scheme described here right now will not employ this optimization (but is otherwise intentionally kept semantically equivalent to JSC):

    1. When a BlockDirectory starts allocating from a new block, it update the the block’s l_newVersion to g_newVersion, and set isNew to true for all already-allocated cells (as the block may not be fully empty), and false for all free cells.
  • Whenever it allocates a cell, it sets its isNew to true.

  • Why do we want to bother setting isNew to true for all already-allocated cells in the block? This is to provide a good property. Since we bump g_newVersion at the end of every GC cycle, due to the scheme above, for any block with latest l_newVersion, a cell is live if and only if its isNew bit is set. Now, when checking if a cell is live, if its l_newVersion is the latest, then we can just return isNew without looking at isMarked, so our logic is simpler.

    The logical versioning for the isMarked bit is similar. At the beginning of a full GC cycle, we bump the g_markVersion to indicate that all mark bits are logically cleared. Note that the global version is not bumped for eden GC, since eden GC does not clear isMark bits.

    There is one extra complexity: the above scheme would break down in an incremental GC. Specifically, during a full GC cycle, we have logically cleared the isMarked bit, but we also didn’t do anything to the isNew bit, so all cells in the old space would appear dead to the allocator. In our old scheme without logical versioning, this case is prevented by doing isNew |= isMarked at the start of the full GC, but we cannot do it now with logical versioning.

    JSC solves this problem with the following clever trick: during a full GC, we should also accept l_markVersion that is off-by-one. In that case, we know the isMarked bit accurately reflects whether or not a cell is live, since that is the result of the last GC cycle. If you are a bit confused, take a look at footnote[21] for a more elaborated case discussion. It might also help to take a look at the comments in the pseudo-code below:

    bool cellContainsLiveObject(BlockFooter* block, size_t cellId) {
        if (block->l_newVersion == g_newVersion) {
            // A latest l_newVersion indicates that the cell is live if
            // and only if its 'isNew' bit is set, so we don't need to
            // look at the 'isMarked' bit even if 'isNew' is false
            return block->isNew[cellId];
        // Now we know isNew bit is logically false, so we should
        // look at the isMarked bit to determine if the object is live
        if (isMarkBitLogicallyCleared(block)) {
            // The isMarked bit is logically false
            return false;
        // The isMarked bit is valid and accurately tells us if
        // the object is live or not
        return block->isMarked[cellId];
    // Return true if the isMarked bitvector is logically cleared
    bool isMarkBitLogicallyCleared(BlockFooter* block) {
        if (block->l_markVersion == g_markVersion) {
            // The mark version is up-to-date, so not cleared
            return false;
        if (IsFullGcRunning() && IsGcInMarkingPhase() &&
            block->l_markVersion == g_markVersion - 1) {
            // We are halfway inside a full GC cycle's marking phase,
            // and the mark version is off-by-one, so the old isMarked bit
            // should be accepted, and it accurately tells us if the
            // object is live or not
            return false;
        return true;

    Before we mark an object in CompleteSubspace, we need to update the l_markVersion of the block holding the cell to the latest, and materialize the isMarked bits of all cells in the block. That is, we need to run the logic at the full GC preparation phase in our old scheme: isNew |= isMarked, isMarked = false for all cells in the block. This is shown below.

    // Used by GC marking phase to mark an object in CompleteSubspace
    void markObject(BlockFooter* block, size_t cellId) {
        block->isMarked[cellId] = true;
    // Materialize 'isMarked' bits if needed
    // To do this, we need to execute the operation at full GC
    // prepare phase: isNew |= isMarked, isMarked = false
    void aboutToMark(BlockFooter* block) {
        if (block->l_markVersion == g_markVersion) {
            // Our mark version is already up-to-date,
            // which means it has been materialized before
        // Check if the isMarked bit is logically cleared to false.
        // The function is defined in the previous snippet.
        if (isMarkBitLogicallyCleared(block)) {
            // This means that the isMarked bitvector should
            // be treated as all false. So operation isNew |= isMarked
            // is no-op, so all we need to do is isMarked = false
            for (size_t cellId = 0; cellId < block->numCells; cellId++) {
                block->isMarked[cellId] = false;
        } else {
            // The 'isMarked' bit is not logically cleared. Now let's
            // check if the 'isNew' bit is logically cleared.
            if (block->l_newVersion < g_newVersion) {
                // The isNew bitvector is logically cleared and should be
                // treated as false. So operation isNew |= isMarked becomes
                // isNew = isMarked (note that executing |= is incorrect
                // beacuse isNew could physically contain true!)
                for (size_t cellId = 0; cellId < block->numCells; cellId++) {
                    block->isNew[cellId] = block->isMarked[cellId];
                    block->isMarked[cellId] = false;
                // We materialized isNew, so update it to latest version
                block->l_newVersion = g_newVersion;
            } else {
                // The l_newVersion is latest, which means that the cell is
                // live if and only if its isNew bit is set.
                // Since isNew already reflects liveness, we do not have to
                // perform the operation isNew |= isMarked (and in fact, it
                // must be a no-op since no dead cell can have isMarked =
                // true). So we only need to do isMarked = false
                for (size_t cellId = 0; cellId < block->numCells; cellId++) {
                    block->isMarked[cellId] = false;
        // We finished materializing isMarked, so update the version
        block->l_markVersion = g_markVersion;

    A fun fact: despite that what we conceptually want to do above is isNew |= isMarked, the above code never performs a |= at all 🙂

    And also, let’s update the pseudo-code for the preparation GC logic:

    Eden GC preparation phase: no work is needed.

    Full GC preparation phase:

    // For PreciseAllocation, we still need to manually do
    // 'isNew |= isMarked, isMarked = false' for every allocation
    for (PreciseAllocation* pa : allPreciseAllocations) {
        pa->isNew |= pa->isMarked;
        pa->isMarked = false;
    // For CompleteSubspace, all we need to do is bump the
    // global version for the 'isMarked' bit

    Eden/Full GC collection phase:

    // For PreciseAllocation, we still need to manually
    // update 'isNew = false' for each allocation, and also
    // free the object if it is dead
    for (PreciseAllocation* pa : allPreciseAllocations) {
        pa->isNew = false;
        if (!pa->isMarked)
    // For CompleteSubspace, all we need to do is bump the
    // global version for the 'isNew' bit

    With logical versioning, the GC no longer sweeps the CompleteSubspace blocks to reclaim dead objects: the reclamation happens lazily, when the allocator starts to allocate from the block. This, however, introduces an unwanted side-effect. Some objects use manual memory management internally: they own additional memory that are not managed by the GC, and have C++ destructors to free that memory when the object is dead. This improves performance as it reduces the work of the GC. However, now we may not immediately sweep dead objects and run destructors, so the memory that is supposed to be freed by the destructor could be kept around indefinitely if the block is never allocated from. To mitigate this issue, JSC will also periodically sweep blocks and run the destructors of the dead objects. This is implemented by IncrementalSweeper, but we will not go into details.

    To conclude, logical versioning provides two important optimizations to the GC scheme:

    1. The so-called “sweep” phase of the GC (to find and reclaim dead objects) is removed for CompleteSubspace objects. The reclamation is done lazily. This is clearly better than sweeping through the block again and again in every GC cycle.
    2. The full GC does not need to reset all isMarked bits in the preparation phase, but only lazily reset them in the marking phase by aboutToMark: this not only reduces work, but also allows the work to be done in parallel and concurrently while the mutator is running, after we make our GC scheme concurrent.

    Optimizing WriteBarrier: The cellState Bit

    As we have explained earlier, whenever the mutator modified a pointer of a marked object o to point to an unmarked object, it needs to add o to the “remembered set”, and this is called the WriteBarrier. In this section, we will dig a bit deeper into the WriteBarrier and explain the optimizations around it.

    The first problem with our current WriteBarrier is that the isMarked bit resides in the block footer, so retrieving its value requires quite a few computations from the object pointer. Also it doesn’t sit in the same CPU cache line as the object, which makes the access even slower. This is undesirable as the cost is paid for every WriteBarrier, regardless of if we add the object to the remembered set.

    The second problem is our WriteBarrier will repeatedly add the same object o to the remembered set every time it is run. The obvious solution is to make rememberedSet a hash set to de-duplicate the objects it contains, but doing a hash lookup to check if the object already exists is far too expensive.

    This is where the last metadata bit that we haven’t explained yet: the cellState bit comes in, which solves both problems.

    Instead of making rememberedSet a hash table, we reserve a byte (though we only use 1 bit of it) named cellState in every object’s object header, to indicate if we might need to put the object into the remembered set in a WriteBarrier. Since this bit resides in the object header as an object field (instead of in the block footer), it’s trivially accessible to the mutator who has the object pointer.

    cellState has two possible values: black and white. The most important two invariants around cellState are the following:

    1. For any object with cellState = white, it is guaranteed that the object does not need to be added to remembered set.
    2. Unless during a full GC cycle, all black (live) objects have isMarked = true.

    Invariant 1 serves as a fast-path: WriteBarrier can return immediately if our object is white, and checking it only requires one load instruction (to load cellState) and one comparison instruction to validate it is white.

    However, if the object is black, a slow-path is needed to check whether it is actually needed to add the object to the remembered set.

    Let’s look at our new WriteBarrier:

    // Executed after writing a pointer to 'dst' into a field of 'obj'
    void WriteBarrier(JSCell* obj) {
        if (obj->cellState == black)

    The first thing to notice is that the WriteBarrier is no longer checking if dst (the object that the pointer points to) is marked or not. Clearly this does not affect the correctness: we are just making the criteria less restrictive. However, the performance impact of removing this dst check is a tricky question without a definite answer, even for JSC developers. Through some preliminary testing, their conclusion is that adding back the !isMarked(dst) check slightly regresses performance. They have two hypotheses. First, by not checking dst, more objects are put into the remembered set and need to be scanned by the GC, so the total amount of work increased. However, the mutator’s work probably decreased, as it does fewer checks and touches fewer cache lines (by not touching the outlined isMarked bit). Of course such benefit is offset because the mutator is adding more objects into the remembered set, but this isn’t too expensive either, as the remembered set is only a segmented vector. The GC has to do more work, as it needs to scan and mark more objects. However, after we make our scheme concurrent, the marking phase of the GC can be done concurrently as the mutator is running, so the latency is probably[22] hidden. Second, JSC’s DFG compiler has an optimization pass that coalesces barriers on the same object together, and it is harder for such barriers to check all the dsts.

    The interesting part is how the invariants above are maintained by the relavent parties. As always, there are three actors: the mutator (WriteBarrier), the allocator, and the GC.

    The interaction with the allocator is the simplest. All objects are born white. This is correct because newly-born objects are not marked, so have no reason to be remembered.

    The interaction with GC is during the GC marking phase:

    1. When we mark an object and push it into the queue, we set its cellState to white.
    2. When we pop an object from the queue, before we start to scan its children, we set its cellState to black.

    In pseudo-code, the Eden/Full GC marking phase now looks like the following (Line 5 and Line 9 are the newly-added logic to handle cellState, other lines unchanged):

    while (!queue.empty() || !rmbSet.empty()) {
        // Both eden GC and full GC needs to consider remembered set
        // Prioritize popping from queue, pop remembered set last
        JSCell* obj = !queue.empty() ? queue.pop() : rmbSet.pop();
        obj->cellState = black;           // <----------------- newly added
        obj->ForEachChild([&](JSCell* child) {
            if (!child->isMarked) {
                child->cellState = white; // <----------------- newly added

    Let’s argue why the invariant is maintained by the above code.

    1. For invariant 1, note that in the above code, an object is white only if it is inside the queue (as once it’s popped out, it becomes black again), pending scanning of its children. Therefore, it is guaranteed that the object will still be scanned by the GC later, so we don’t need to add the object to remembered set, as desired.
    2. For invariant 2, at the end of any GC cycle, any live object is marked, which means it has been scanned, so it is black, as desired.

    Now let’s look at what WriteBarrierSlowPath should do. Clearly, it’s correct if it simply unconditionally add the object to remembered set, but that also defeats most of the purpose of cellState as an optimization mechanism: we want something better. A key use case of cellState is to prevent adding an object into the remembered set if it is already there. Therefore, after we put the object into the remembered set, we will set its cellState to white, like shown below.

    void WriteBarrierSlowPath(JSCell* obj) { 
        obj->cellState = white;

    Let’s prove why the above code works. Once we added an object to remembered set, we set it to white. We don’t need to add the same object into the remembered set until it gets popped out from the set by GC. But when GC pops out the object, it would set its cellState back to black, so we are good.

    JSC employed one more optimization. During a full GC, we might see a black object that has isMarked = false (note that this is the only possible case that the object is unmarked, due to invariant 2). In this case, it’s unnecessary to add the object to remembered set, since the object will eventually be scanned in the future (or it became dead some time later before it was scanned, in which case we are good as well). Furthermore, we can flip it back to white, so we don’t have to go into this slow path the next time a WriteBarrier on this object runs. To sum up, the optimized version is as below:

    void WriteBarrierSlowPath(JSCell* obj) {
        if (IsFullGcRunning()) {
            if (!isMarked(obj)) {
                // Do not add the object to remembered set
                // In addition, set cellState to white so this
                // slow path is not triggered on the next run
                obj->cellState = white;
        } else {
            assert(isMarked(obj)); // due to invariant 2
        obj->cellState = white;

    Getting Concurrent and Getting Wild

    At this point, we already have a very good incremental and generational garbage collector: the mutator, allocator and GC all have their respective fast-paths for the common cases, and with logical versioning, we avoided redundant work as much as possible. In my humble opinion, this is a good balance point between performance and engineering complexity.

    However, because JSC is one of the core drivers of performance in Safari, it’s unsurprising that performance is a top priority, even at the cost of engineering complexity. To squeeze out every bit of performance, JSC made their GC concurrent. This is no easy feat: due to the nature of GCs, it’s often too slow to use locks to protect against certain race conditions, so extensive lock-free programming is employed.

    But once lock-free programming is involved, one starts to get into all sorts of architecture-dependent memory reordering problems. x86-64 is the more strict architecture: it only requires StoreLoadFence(), and it provides TSO-like semantics. JSC also supports ARM64 CPUs, which has even fewer guarantees: load-load, load-store, store-load, and store-store can all be reordered by the CPU, so a lot more operations need fences. As if things were not bad enough, for performance reasons, JSC often avoids using memory fences on ARM64. They have the so-called Dependency class, which creates an implicit CPU data dependency on ARM64 through some scary assembly hacks, so they can get the desired memory ordering for a specific data-flow without paying the cost of a memory fence. As you can imagine, with all of these complications and optimizations, the code can become difficult to read.

    So due to my limited expertise, it’s unsurprising if I missed to explain or mis-explained some important race conditions in the code, especially some ARM64-specific ones: if you spotted any issue in this post, please let me know.

    Let’s go through the concurrency assumptions first. JavaScript is a single-threaded language, so there is always only one mutator thread[23]. Apart from the mutator thread, JSC has a bunch of compilation threads, a GC thread, and a bunch of marking threads. Only the GC marking phase is concurrent: during which the mutator thread, the compiler threads, and a bunch of marking threads are concurrently running (yes, the marking itself is also done in parallel). However, all the other GC phases are run with the mutator thread and compilation threads stopped.

    Some Less Interesting Issues

    First of all, clearly the isMarked and isNew bitvector must be made safe for concurrent access, since multiple threads (including marking threads and mutator) may concurrently update it. Using CAS with appropriate retry/bail mechanism is enough for the bitvector itself.

    BlockFooter is harder, and needs to be protected with a lock: multiple threads could be simultaneously calling aboutToMark(), so aboutToMark() must be guarded. For the reader side (the isMarked() function, which involves first checking if l_markVersion is latest, then reading the isMarked bitvector), in x86-64 thanks to x86-TSO, one does not need a lock or any memory fence (as long as aboutToMark takes care to update l_markVersion after the bitvector). In ARM64, since load-load reordering is allowed, a Dependency is required.

    Making the cellContainsLiveObject (or in JSC jargon, isLive) check lock-free is harder, since it involves potentially reading both the isMarked bit and the isNew bit. JSC employs optimistic locking to provide a fast-path. This is not very different from an optimistic locking scheme you can find in a textbook, so I won’t dive into the details.

    Of course, there are a lot more subtle issues to change. Almost all the pseudo-code above needs to be adapted for concurrency, either by using a lock or CAS, or by using some sort of memory barrier and concurrency protocol to ensure that the code works correctly under races. But now let’s turn to some more important and tricky issues.

    The Race Between WriteBarrier and Marking

    One of the most important races is the race between WriteBarrier and GC’s marking threads. The marking threads and the mutator thread can access the cellState of an object concurrently. For performance reasons, a lock is infeasible, so a race condition arises.

    It’s important to note that we call WriteBarrier after we have written the pointer into the object. This is not only more convenient to use (especially for JIT-generated code), but also allows a few optimizations: for example, in certain cases, multiple writes to the same object may only call WriteBarrier once at the end.

    With this in mind, let’s analyze why our current implementation is buggy. Suppose o is an object, and the mutator wants to store a pointer to another object target into a field f of o. The marking logic of the GC wants to scan o and append its children into the queue. We need to make sure that GC will observe the o->target pointer link.

    Let’s first look at the correct logic:

    Mutator (WriteBarrier) GC (Marker)
    Store(o.f, target)
    StoreLoadFence() // WriteBarrier begin
    t1 = Load(o.cellState)
    if (t1 == black): WriteBarrierSlowPath(o)
    Store(o.cellState, black)
    t2 = Load(o.f) // Load a children of o
    Do some check to t2 and push it to queue

    This is mostly just a copy of the pseudocode in the above sections, except that we have two StoreLoadFence(). A StoreLoadFence() guarantees that no LOAD after the fence may be executed by the CPU out-of-order engine until all STORE before the fence have completed. Let’s first analyze what could go wrong without either of the fences.

    Just to make things perfectly clear, the precondition is o.cellState = white (because o is in the GC’s queue) and o.f = someOldValue.

    What could go wrong if the mutator WriteBarrier doesn’t have the fence? Without the fence, the CPU can execute the LOAD in line 3 before the STORE in line 1. Then, in the following interleaving:

    1. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = white
    2. [GC Line 1] Store(o.cellState, black)
    3. [GC Line 3] t2 = Load(o.f)                 // t2 = some old value
    4. [Mutator Line 1] Store(o.f, target)

    Now, the mutator did not add o to remembered set (because t1 is white, not black), and t2 in GC is the old value in o.f instead of target, so GC did not push target into the queue. So the pointer link from o to target is missed in GC. This can result in target being wrongly reclaimed despite it is live.

    And what could go wrong if the GC marking logic doesn’t have the fence? Similarly, without the fence, the CPU can execute the LOAD in line 3 before the STORE in line 1. Then, in the following interleaving:

    1. [GC Line 3] t2 = Load(o.f)                 // t2 = some old value
    2. [Mutator Line 1] Store(o.f, target)
    3. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = white
    4. [GC Line 1] Store(o.cellState, black)

    Similar to above, mutator sees t1 = white and GC sees t2 = oldValue. So o is not added to remembered set, and target is not pushed into the queue, the pointer link is missed.

    Finally, let’s analyze why the code behaves correctly if both fences are present. Unfortunately there is not a better way than manually enumerating all the interleavings. Thanks to the fences, Mutator Line 1 must execute before Mutator Line 3, and GC Line 1 must execute before GC Line 3, but the four lines can otherwise be reordered arbitrarily. So there are 4! / 2! / 2! = 6 possible interleavings. So let’s go!

    Interleaving 1:

    1. [Mutator Line 1] Store(o.f, target)
    2. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = white
    3. [GC Line 1] Store(o.cellState, black)
    4. [GC Line 3] t2 = Load(o.f)                 // t2 = target

    In this interleaving, the mutator did not add o to remembered set, but the GC sees target, so it’s fine.

    Interleaving 2:

    1. [GC Line 1] Store(o.cellState, black)
    2. [GC Line 3] t2 = Load(o.f)                // t2 = some old value
    3. [Mutator Line 1] Store(o.f, target)
    4. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = black

    In this interleaving, the GC saw the old value, but the mutator added o to the remembered set, so the GC will eventually drain from the remembered set and scan o again, at which time it will see the correct new value target, so it’s fine.

    Interleaving 3:

    1. [Mutator Line 1] Store(o.f, target)
    2. [GC Line 1] Store(o.cellState, black)
    3. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = black
    4. [GC Line 3] t2 = Load(o.f)                 // t2 = target

    In this interleaving, the GC saw the new value target, nevertheless, the mutator saw t1 = black and added o to the remembered set. This is unfortunate since the GC will scan o again, but it doesn’t affect correctness.

    Interleaving 4:

    1. [Mutator Line 1] Store(o.f, target)
    2. [GC Line 1] Store(o.cellState, black)
    3. [GC Line 3] t2 = Load(o.f)                 // t2 = target
    4. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = black

    Same as Interleaving 3.

    Interleaving 5:

    1. [GC Line 1] Store(o.cellState, black)
    2. [Mutator Line 1] store(o.f, target)
    3. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = black
    4. [GC Line 3] t2 = Load(o.f)                // t2 = target

    Same as Interleaving 3.

    Interleaving 6:

    1. [GC Line 1] Store(o.cellState, black)
    2. [Mutator Line 1] Store(o.f, target)
    3. [GC Line 3] t2 = Load(o.f)                 // t2 = target
    4. [Mutator Line 3] t1 = Load(o.cellState)    // t1 = black

    Same as Interleaving 3.

    This proves that with the two StoreLoadFence(), our code is no longer vulnerable to the above race condition.

    Another Race Condition Between WriteBarrier and Marking

    The above fix alone is not enough: there is another race between WriteBarrier and GC marking threads. Recall that in WriteBarrierSlowPath, we attempt to flip the object back to white if we saw it is not marked (this may happen during a full GC), as illustrated below:

    ... omitted ...
    if (!isMarked(obj)) {
        obj->cellState = white;
    ... omitted ...

    It turns out that, after setting the object white, we need to do a StoreLoadFence(), and check again if the object is marked. If it becomes marked, we need to set obj->cellState back to black.

    Without the fix, the code is vulnerable to the following race:

    1. [Precondition] o.cellState = black and o.isMarked = false
    2. [WriteBarrier] Check isMarked()                 // see false
    3. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    4. [GC Marking] Popped ‘o’ from queue, Store(o.cellState, black)
    5. [WriteBarrier] Store(o.cellState, white)
    6. [Postcondition] o.cellState = white and o.isMarked = true

    The post-condition is bad because o will not be added to the remembered set in the future, despite that it needs to be (as the GC has already scanned it).

    Let’s now prove why the code is correct when the fix is applied. Now the WriteBarrier logic looks like this:

    1. [WriteBarrier] Store(o.cellState, white)
    2. [WriteBarrier] t1 = isMarked()
    3. [WriteBarrier] if (t1 == true): Store(o.cellState, black)

    Note that we omitted the first “Check isMarked()” line because it must be the first thing executed in the interleaving, as otherwise the if-check won’t pass at all.

    The three lines in WriteBarrier cannot be reordered by CPU: Line 1-2 cannot be reordered because of the StoreLoadFence(), line 2-3 cannot be reordered since line 3 is a store that is only executed if line 2 is true. The two lines in GC cannot be reordered by CPU because line 2 stores to the same field o.cellState as line 1.

    In addition, note that it’s fine if at the end of WriteBarrier, the object is black but GC has only executed to line 1: this is unfortunate, because the next WriteBarrier on this object will add the object to the remembered set despite it being unnecessary. However, it does not affect our correctness. So now, let’s enumerate all the interleavings again!

    Interleaving 1.

    1. [WriteBarrier] Store(o.cellState, white)
    2. [WriteBarrier] t1 = isMarked() // t1 = false
    3. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // not executed

    Object is not marked and white, OK.

    Interleaving 2.

    1. [WriteBarrier] Store(o.cellState, white)
    2. [WriteBarrier] t1 = isMarked() // t1 = false
    3. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    4. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // not executed

    Object is in queue and white, OK.

    Interleaving 3.

    1. [WriteBarrier] Store(o.cellState, white)
    2. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    3. [WriteBarrier] t1 = isMarked() // t1 = true
    4. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // executed

    Object is in queue and black, unfortunate but OK.

    Interleaving 4.

    1. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    2. [WriteBarrier] Store(o.cellState, white)
    3. [WriteBarrier] t1 = isMarked() // t1 = true
    4. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // executed

    Object is in queue and black, unfortunate but OK.

    Interleaving 5.

    1. [WriteBarrier] Store(o.cellState, white)
    2. [WriteBarrier] t1 = isMarked() // t1 = false
    3. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    4. [GC Marking] Popped ‘o’ from queue, Store(o.cellState, black)
    5. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // not executed

    Object is marked and black, OK.

    Interleaving 6.

    1. [WriteBarrier] Store(o.cellState, white)
    2. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    3. [WriteBarrier] t1 = isMarked() // t1 = true
    4. [GC Marking] Popped ‘o’ from queue, Store(o.cellState, black)
    5. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // executed

    Object is marked and black, OK.

    Interleaving 7.

    1. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    2. [WriteBarrier] Store(o.cellState, white)
    3. [WriteBarrier] t1 = isMarked() // t1 = true
    4. [GC Marking] Popped ‘o’ from queue, Store(o.cellState, black)
    5. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // executed

    Object is marked and black, OK.

    Interleaving 8.

    1. [WriteBarrier] Store(o.cellState, white)
    2. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    3. [GC Marking] Popped ‘o’ from queue, Store(o.cellState, black)
    4. [WriteBarrier] t1 = isMarked() // t1 = true
    5. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // executed

    Object is marked and black, OK.

    Interleaving 9.

    1. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    2. [WriteBarrier] Store(o.cellState, white)
    3. [GC Marking] Popped ‘o’ from queue, Store(o.cellState, black)
    4. [WriteBarrier] t1 = isMarked() // t1 = true
    5. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // executed

    Object is marked and black, OK.

    Interleaving 10.

    1. [GC Marking] CAS(o.isMarked, true), Store(o.cellState, white), pushed ‘o’ into queue
    2. [GC Marking] Popped ‘o’ from queue, Store(o.cellState, black)
    3. [WriteBarrier] Store(o.cellState, white)
    4. [WriteBarrier] t1 = isMarked() // t1 = true
    5. [WriteBarrier] if (t1 == true): Store(o.cellState, black)   // executed

    Object is marked and black, OK.

    So let’s update our pseudo-code. However, I would like to note that, in JSC’s implementation, they did not use a StoreLoadFence() after obj->cellState = white. Instead, they made the obj->cellState = white a CAS from black to white (with memory ordering memory_order_seq_cst). This is stronger than a StoreLoadFence() so their logic is also correct. Nevertheless, just in case my analysis above missed some other race with other components, our pseudo-code will stick to their logic…

    Mutator WriteBarrier pseudo-code:

    void WriteBarrier(JSCell* obj) {
        StoreLoadFence(); // Note the fence!
        if (obj->cellState == black)
    void WriteBarrierSlowPath(JSCell* obj) {
        if (IsGcRunning()) {
            if (!isMarked(obj)) {
                if (SUCCESS == 
                    CompareAndSwap(obj->cellState, black /*from*/, white /*to*/)) {
                    if (isMarked(obj)) {
                        obj->cellState = black;
        } else {
        obj->cellState = white;
        // Add 'obj' to remembered set

    Eden/Full GC Marking phase:

    while (!queue.empty() || !rmbSet.empty()) {
        JSCell* obj = !queue.empty() ? queue.pop() : rmbSet.pop();
        obj->cellState = black;
        StoreLoadFence(); // Note the fence!
        obj->ForEachChild([&](JSCell* child) {
            if (!child->isMarked) {
                child->cellState = white;

    Remove Unnecessary Memory Fence In WriteBarrier

    The WriteBarrier is now free of hazardous race conditions. However, we are executing a StoreLoadFence() for every WriteBarrier, which is a very expensive CPU instruction. Can we optimize it?

    The idea is the following: the fence is used to protect against race with GC. Therefore, we definitely need the fence if the GC is concurrently running. However, the fence is unnecessary if the GC is not running. Therefore, we can check if the GC is running first, and only execute the fence if the GC is indeed running.

    JSC is even more clever: instead of having two checks (one that checks if the GC is running and one that checks if the cellState is black), it combines them into a single check for the fast-path where the GC is not running and the object is white. The trick is the following:

    1. Assume black = 0 and white = 1 in the cellState enum.
    2. Create a global variable called blackThreshold. This blackThreshold is normally 0, but at the beginning of a GC cycle, it will be set to 1, and it will be reset back to 0 at the end of the GC cycle.
    3. Now, check if obj->cellState > blackThreshold.

    Then, if the check succeeded, we know we can immediately return: the only case this check can succeed is when the GC is not running and we are white (because blackThreshold = 0 and cellState = 1 is the only situation to pass the check). This way, the fast path only executes one check. If the check fails, then we fallback to the slow path, which performs the full procedure: check if GC is running, execute a fence if needed, then check if cellState is black again. In pseudo-code:

    void WriteBarrier(JSCell* obj) {
        if (obj->cellState > g_blackThreshold) {
            // Fast-path: the only way to reach here is when
            // the GC is not running and the cellState is white
        if (!IsGcRunning()) {
            // g_blackThreshold is 0, so our object is
            // actually black, we need to go to WriteBarrierSlowPath
        } else {
            // GC is running so we need to execute the fence
            // and check cellState again
            if (obj->cellState == black) {

    Note that there is no race between WriteBarrier and GC setting/clearing IsGcRunning() flag and changing the g_blackThreshold value, because the mutator is always stopped at a safe point (of course, halfway inside WriteBarrier is not a safe point) when the GC starts/finishes.

    “Obstruction-Free Double Collect Snapshot”

    The concurrent GC also introduced new complexities for the ForEachChild function used by the GC marking phase to scan all objects referenced by a certain object. Each JavaScript object has a Structure (aka, hidden class) that describes how the content of this object shall be interpreted into object fields. Since the GC marking phase is run concurrently with the mutator, and the mutator may change the Structure of the object, and may even change the size of the object’s butterfly, the GC must be sure that despite the race conditions, it will never crash by dereferencing invalid pointers and never miss to scan a child. Using a lock is clearly infeasible for performance reasons. JSC uses a so-called obstruction-free double collect snapshot to solve this problem. Please refer to Filip Pizlo’s GC blog post to see how it works.

    Some Minor Design Details and Optimizations

    You might find this section helpful if you want to actually read and understand the code of JSC, but otherwise feel free to skip it: these details are not centric to the design, and are not particularly interesting either. I mention them only to bridge the gap between the GC scheme explained in this post and the actual implementation in JSC.

    As explained earlier, each CompleteSubspace owns a list of BlockDirectory to handle allocations of different sizes; each BlockDirectory has an active block m_currentBlock where it allocates from, and it achieves this by holding a free list of all available cells in the block. But how does it work exactly?

    As it turns out, each BlockDirectory has a cursor, which is reset to point at the beginning of the block list at the end of an eden or full GC cycle. Until it is reset, it can only move forward. The BlockDirectory will move the cursor forward, until it finds a block containing available cells, and allocate from it. If the cursor reaches the end of the list, it will attempt to steal a 16KB block from another BlockDirectory and allocate from it. If that also fails, it will allocate a new 16KB block from malloc and allocate from it.

    I also mentioned that a BlockDirectory uses a free list to allocate from the currently active block m_currentBlock. It’s important to note that in the actual implementation of JSC, the cells in m_currentBlock does not respect the rule for isNew bit. Therefore, to check liveness, one either needs to do a special-case check to see if the cell is from m_currentBlock (for example, see HeapCell::isLive), or, for the GC[24], stop the mutator, destroy the free list (and populate isNew in the process), do whatever inspection, then rebuild the free list and resume the mutator. The latter is implemented by two functions named stopAllocating() and resumeAllocating(), which are automatically called whenever the world is stopped or resumed.

    The motivation of allowing m_currentBlock to not respect the rule for isNew is (a tiny bit of) performance. Instead of manually setting isNew to true for every allocation, a block-level bit allocated (aggregated as a bitvector in BlockDirectory) is used to indicate if a block is full of live objects. When the free list becomes empty (i.e., the block is fully allocated), we simply set allocated to true for this block. When querying cell liveness, we check this bit first and directly return true if it is set. The allocated bitvector is cleared at the end of each GC cycle, and since the global logical version for isNew is also bumped, this effectively clears all the isNew bits, just as we desired.

    JSC’s design also supports the so-called constraint solver, which allows specification of implicit reference edges (i.e., edges not represented as pointer in the object). This is mainly used to support JavaScript interaction with DOM. This part is not covered in this post.

    Weak references have multiple implementations in JSC. The general (but less efficient) implementation is WeakImpl, denoting a weak reference edge. The data structure managing them is WeakSet, and you can see it in every block footer, and in every PreciseAllocation GC header. However, JSC also employs more efficient specialized implementations to handle the weak map feature in JavaScript. The details are not covered in this post.

    In JSC, objects may also have destructors. There are three ways destructors are run. First, when we begin allocating from a block, destructors of the dead cells are run. Second, the IncrementalSweeper periodically scans the blocks and runs destructors. Finally, when the VM shuts down, the lastChanceToFinalize() function is called to ensure that all destructors are run at that time. The details of lastChanceToFinalize() are not covered in this post.

    JSC employs a conservative approach for pointers on the stack and in registers: the GC uses UNIX signals to suspend the mutator thread, so it can copy its stack contents and CPU register values to search for data that looks like pointers. However, it’s important to note that a UNIX signal is not used to suspend the execution of the mutator: the mutator always actively suspends itself at a safe point. This is critical, as otherwise it could be suspended at weird places, for example, in a HeapCell::isLive check after it has read isNew but before it has read isMarked, and then GC did isNew |= isMarked, isMarked = false, and boom. So it seems like the only reason to suspend the thread is for the GC to get the CPU register values, including the SP register value so the GC knows where the stack ends. It’s unclear to me if it’s possible to do so in a cooperative manner instead of using costly UNIX signals.


    I thank Saam Barati from Apple’s JSC team for his enormous help on this blog post. Of course, any mistakes in this post are mine.


    1. A brief stop-the-world pause is still required at the start and end of each GC cycle, and may be intentionally performed if the mutator thread (i.e.the thread running JavaScript code) is producing garbage too fast for the GC thread to keep up with. ↩︎
    2. The actual allocation logic is implemented in LocalAllocator. Despite that in the code BlockDirectory is holding a linked list of LocalAllocator, (at time of writing, for the codebase version linked in this blog) the linked list always contains exactly one element, so the BlockDirectory and LocalAllocator is one-to-one and can be viewed as an integrated component. This relationship might change in the future, but it doesn’t matter for the purpose of this post anyway. ↩︎
    3. Since the footer resides at the end of a 16KB block, and the block is also 16KB aligned, one can do a simple bit math from any object pointer to access the footer of the block it resides in. ↩︎
    4. Similar to that per-cell information is aggregated and stored in the block footer, per-block information is aggregated as bitvectors and stored in BlockDirectory for fast lookup. Specifically, two bitvectors empty and canAllocateButNotEmpty track if a block is empty, or partially empty. The code is relatively confusing because the bitvectors are laid out in a non-standard way to make resizing easier, but conceptually it’s just one bitvector for each boolean per-block property. ↩︎
    5. While seemingly straightforward, it is not straightforward at all (as you can see in the code). The free cells are marked free by the GC, and due to concurrency and performance optimization the logic becomes very tricky: we will revisit this later. ↩︎
    6. In fact, it also attempts to steal blocks from other allocators, and the OS memory allocator may have some special requirements required for the VM, but we ignore those details for simplicity. ↩︎
    7. In the current implementation, the list of sizes (byte) are 16, 32, 48, 64, 80, then 80 * 1.4 ^ n for n >= 1 up to about 8KB. Exponential growth guarantees that the overhead due to internal fragmentation is at most a fraction (in this case, 40%) of the total allocation size. ↩︎
    8. An interesting implementation detail is that IsoSubspace and CompleteSubspace always return memory aligned to 16 bytes, but PreciseAllocation always return memory address that has reminder 8 module 16. This allows identifying whether an object is allocated by PreciseAllocation with a simple bit math. ↩︎
    9. JSC has another small optimization here. Sometimes a IsoSubspace contains so few objects that it’s a waste to hold them using a 16KB memory page (the block size of BlockDirectory). So the first few memory pages of IsoSubspace use the so-called “lower-tier”, which are smaller memory pages allocated by PreciseAllocation. In this post, we will ignore this design detail for simplicity. ↩︎
    10. Memory of an IsoSubspace is only used by this IsoSubspace, never stolen by other allocators. As a result, a memory address in IsoSubspace can only be reused to allocate objects of the same type. So for any type A allocated by IsoSubspace, even if there is a use-after-free bug on type A, it is impossible to allocate A, free it, allocate type B at the same address, and exploit the bug to trick the VM into interpreting an integer field in B controlled by attacker as a pointer field in A. ↩︎
    11. In some GC schemes, an eden object is required to survive two (instead of one) eden GCs to be considered in old space. The purpose of such design is to make sure that any old space object is at least one eden-GC-gap old. In contrast, in JSC’s design, an object created immediately before an eden collection will be considered to be in old space immediately, which then can only be reclaimed via a full GC. The performance difference between the two designs is unclear to me. I conjecture JSC chose its current design because it’s easier to make concurrent. ↩︎
    12. There is one additional color Grey in the code. However, it turns out that White and Grey makes no difference (you can verify it by grepping all use of cellState and observe that the only comparison on cellState is checking if it is Black). The comments explaining what the colors mean do not fully capture all the invariants. In my opinion JSC should really clean it up and update the comment, as it can easily cause confusion to readers who intend to understand the design. ↩︎
    13. The bit is actually called isNewlyAllocated in the code. We shorten it to isNew for convenience in this post. ↩︎
    14. Safe point is a terminology in GC. At a safe point, the heap and stack is in a coherent state understandable by the GC, so the GC can correctly trace out which objects are dead or live. ↩︎
    15. For PreciseAllocation, all allocated objects are chained into a linked list, so we can traverse all objects (live or dead) easily. This is not efficient: we will explain the optimizations for CompleteSubspace later. ↩︎
    16. Keep in mind that while this is true for now, as we add more optimizations to the design, this will no longer be true. ↩︎
    17. Note that we push the old space object into the queue, not the eden object, because this pointer could have been overwritten at the start of the GC cycle, making the eden object potentially collectable. ↩︎
    18. Also note that all objects dead before this GC cycle, i.e. the free cells of a block in CompleteSubspace, still have isNew = false and isMarked = false, as desired. ↩︎
    19. Recall that under generational hypothesis, most objects die young. Therefore, that “all objects in an eden block are found dead during eden GC” is something completely plausible. ↩︎
    20. In JSC, the version is stored in a uint32_t and they have a bunch of logic to handle the case that it overflows uint32_t. In my humble opinion, this is an overoptimization that results in very hard-to-test edge cases, especially in a concurrent setting. So we will ignore this complexity: one can easily avoid these by spending 8 more bytes per block footer to have uint64_t version number instead. ↩︎
    21. Note that any number of eden GC cycles may have run between the last full GC cycle and the current full GC cycle, but eden GC does not bump mark version. So for any object born before the last GC cycle (no matter eden or full), the isMarked bit honestly reflects if it is live, and we will accept the bit as its mark version must be off-by-one. For objects born after the last GC cycle, it must have a latest isNew version, so we can know it’s alive through isNew. In both cases, the scheme correctly determines if an object is alive, just as desired. ↩︎
    22. And probably not: first, true sharing and false sharing between GC and mutator can cause slowdowns. Second, as we have covered before, JSC uses a Time-Space Scheduler to prevent the mutator from allocating too fast while the GC is running. Specifically, the mutator will be intentionally suspended for at least 30% of the duration. So as long as the GC is running, the mutator suffers from an 30%-or-more “performance tax”. ↩︎
    23. The real story is a bit more complicated. JSC actually reuse the same VM for different JavaScript scripts. However, at any moment, at most one of the script can be running. So technically, there are multiple mutually-exclusive mutator threads, but this doesn’t affect our GC story. ↩︎
    24. The GC needs to inspect a lot of cells, and its logic is already complex enough, so having one less special-case branch is probably beneficial for both engineering and performance. ↩︎

    July 29, 2022 04:00 PM

    July 28, 2022

    WPE WebKit Blog: WPE QA and tooling

    Igalia WebKit

    In the previous posts, my colleagues Claudio and Miguel wrote respectively about the major components of the project and, specifically, the graphics architecture of WPE. Today, you’ll see our efforts to improve the quality of both WPE and the experience of working and using it. While the previous entries in this blog post series about WPE aren’t necessarily required in order to read this one, we recommend you to starting with the first post in the series.

    Automated testing

    Testing is an essential part of the WebKit project, primarily due to the large number of use cases covered by HTML/CSS/Javascript specifications and the need for the project to work correctly in a wide range of configurations.

    As an official port of WebKit, WPE uses the former’s testing infrastructure, based on BuildBot. There are two primary servers, one working as an early warning system by testing the patches before they’re committed to the main repository, and another for more extensive testing after accepting the incoming changes. screenshot

    Currently, the WPE testing bots target debug and release configurations using the Flatpak SDK (more on it later in this article) on 64bit Intel-based Linux Debian systems. We have plans of adding bots running on Raspberry Pi boards in the future. Alongside nightly testing, we keep builder bots covering the Ubuntu LTS/Debian versions we support. After August 14th, 2022, the earliest supported versions will be Ubuntu 20.04 LTS and Debian 11 (Bullseye).

    Test suites

    Initially, the WPE builder bots build WPE in both release and debug configurations and feed the built packages into the tester bots, which run some test suites according to their configuration, each suite focused in one aspect of the project:

    • Layout tests: The main suite tests whether WebKit correctly renders web pages and its implementation of web APIs. This suite comprises both WebKit’s test cases and the imported tests from Web Platform Test. At the time of writing, it runs over 50,000 test cases.
    • API Tests: This suite tests the API provided to developers by WebKit and its ports. For example, this step tests the WPE API used in Cog.
    • Javascriptcore tests: Covers the JavascriptCore engine, running WebKit’s tests alongside test262, the reference test suite for JS/ECMAScript implementations.
    • WebDriver: Tests from Selenium and W3C WebDriver APIs for browser automation.
    • Other small suites: Tests for WebKit’s tooling components.

    Due to a large number of tests and the fast development of both WebKit and the specifications—it’s not uncommon to have dozens of daily commits touching dozens of tests—it’s hard to keep the testing bots green.

    For example, while we try to make the tests work on all platforms, many old layout tests use the -expected.txt scheme, where the render tree is printed in a textual format with the text sized in pixels for every node. While this works fine in most cases, many tests have minor differences between the expected result in the Mac platform and the WPE/GTK platform. One of the causes is the font rendering particularities of each port.

    Thankfully, this situation improved significantly since the beginning of the project. Among the efforts, many tests are now using a “reference” HTML file, which are HTML files that render to the same expected result as the test case, so both the test case and the reference will use the same font rendering scheme and can be compared pixel by pixel.

    Building and running WPE

    This section focuses on the experience of building and running WPE in a regular Linux x86–64 system. In a future post, we’ll cover building for and running on embedded devices.

    Checking out the code

    Recently, WebKit moved to GitHub, so you can clone it directly from there:

    $ mkdir ~/dev
    $ git clone

    Note: Due to the size of the project history, you might want to use --depth=1 to clone a single revision, followed by git pull --unshallow from inside the cloned repository to fetch the history if needed.

    There’s more information in WebKit’s GitHub wiki about setting up the git checkout for contributing code back to WebKit. It’ll set up some git hooks to do some tasks required by the project, like formatting the commit message and automatically linking the pull request to the Bugzilla issue.

    All commands in the following sections are run from inside the cloned repository.

    Updating the dependencies (aka The WebKit Flatpak SDK)

    Like most complex software projects, WebKit has a reasonably extensive list of dependencies. Keeping a reference set of their versions frozen during development is desirable to make it easier to reproduce bugs and test results. In older times, WPE and WebKitGTK used JHBuild to freeze a set of dependencies. While this worked for a long time, it did not cover all dependencies. Sometimes, there could be minor differences in the layout tests between the reference test bots and the developer machine due to some dependency resolved by the host system outside JHBuild.

    To improve reproducibility, since 2020, WPE and WebKitGTK have been using an SDK based on Flatpak (kudos to my colleagues Thibault Saunier and Philippe Normand), with a much more extensive dependency coverage and isolation from the host system. Alongside the dependencies, it ships some tools like rr and supports tools like clangd. Almost all bots enable this SDK, the exception being the LTS/Stable bots; as in the latter, we want to build with the already available packages in each distribution.

    $ ./Tools/Scripts/update-webkit-flatpak

    The command will set up the local flatpak repository at ./WebKitBuild/UserFlatpak with the downloaded SDK and create some bundled icecc toolchains. This enables distributed builds in local networks…


    Once the SDK download finishes, you can use the helper script ./Tools/Scripts/build-webkit, which wraps the cmake command with some pre-set options commonly used in normal development, like enabling developer-only features usually disabled in regular builds. Manually invoking cmake is possible, although usually only when you want more control over the build. To build WPE in release mode, use:

    $ ./Tools/Scripts/build-webkit --release --wpe

    Optionally, you can pass it multiple arguments to be fed directly to make or cmake with the switches --makeargs=... and --cmakeargs=..., respectively. For example, --makeargs="-j8" will limit make to 8 parallel jobs and --cmakeargs="-DENABLE_GAMEPAD=1" will enable gamepad support (requires libmanette, bundled in the SDK).

    The first build might take a while (up to almost one hour in a regular laptop). Fortunately, the SDK uses ccache to avoid recompiling the same object files, so subsequent builds without significant changes usually are faster. For more info on speeding the build, check the wiki.

    Running the browser (Cog)

    To run Cog, the reference WPE browser, you need a Wayland server, which is common in most Linux systems nowadays.

    $ ./Tools/Scripts/run-minibrowser --wpe --release
    Cog with GTK4 shell screenshot

    Running some tests

    To run the API tests, which reside in Tools/TestWebKitAPI/Tests/, you can use the following command:

    $ ./Tools/Scripts/run-wpe-tests --release --display-server=headless

    Other test suites:

    • Layout tests: ./Tools/Scripts/run-wpe-tests
    • JSC tests: ./Tools/Scripts/run-javascriptcore-tests
    • WebDriver: ./Tools/Scripts/run-webdriver-tests

    As stated when we described the test suites, the main challenge in testing is keeping up with the fast pace of development, as it’s not uncommon to have some revisions updating hundreds of tests.

    Contributing code to WPE

    After hacking locally, you can submit your changes following the workflow listed in the WebKit wiki.

    Testing WPE in the wild

    If you don’t want to build your WPE build or image, there are some options to get a taste of WPE listed on our website, including:

    Some of these options, like the prebuilt images and the Balena blocks, will be the subject of future blog posts in this series.

    Final thoughts

    With this, we conclude this brief overview of WPE automated testing and the main tools we use in our daily work with WPE. In future posts in this area we’ll go deeper in other subjects like testing on embedded boards and debugging practices.

    If this post got you interested in collaborating with WPE development, or you are in need of a web engine to run on your embedded device, feel free to contact us. We’ll be pleased to help!

    We also have open positions at the WebKit team at Igalia. If you’re motivated by this field and you’re interested in developing your career around it, you can apply here!

    July 28, 2022 12:00 AM

    July 27, 2022

    Release Notes for Safari Technology Preview 150

    Surfin’ Safari

    Safari Technology Preview Release 150 is now available for download for macOS Monterey 12.3 or later and macOS Ventura beta. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

    This release includes WebKit changes between:

    Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

    Web Inspector

    • Elements Tab
      • Changed CSS autocomplete so that the most commonly used property accounts for fuzzy-matching (252194@main)
      • Fixed property usage counts to ignore variables in CSS autocomplete (252195@main)
      • Make popover for resolved CSS variable values consistent with other popovers (252133@main)
      • Automatically hide the CSS documentation popover when navigating away (252175@main)
    • Sources Tab
      • Provided an option for controlling whether local overrides entirely replace or have some passthrough (251884@main)
    • Network Tab
    • Timelines Tab
      • Fixed Screenshots timeline overview records to adjust their position when zooming in or zooming out (252148@main)
    • Layers Tab
      • Fixed reasons for compositing for an element with clip-path and composited descendants (252354@main)
    • Search Tab
      • Improved search performance for short queries on many sites (252279@main)
    • Device Settings and Develop menu
      • Updated user-agent overrides to match options in Safari 16 (251881@main)


    Shadow DOM

    • Fixed :focus pseudo-class failing to repaint on a shadow host when the focus moves from outside the page to inside a shadow tree (252261@main)
    • Fixed :focus-within pseudo-class not getting updated when the frame loses or gains focus (252324@main)
    • Fixed :lang pseudo-class to work across shadow boundaries (252099@main)
    • Fixed the :host() function pseudo-class to only accept a single compound-selector (252110@main)
    • Fixed vw units to get updated inside shadow trees as the frame is resized (252149@main)


    • Adjusted ArrayBuffer size tracking only when full garbage collection happens (252368@main)
    • Adopted Intl.NumberFormat v3 spec change for useGrouping option (252013@main)
    • Fixed interpretation of the fractional part of an ISO8601 Duration string (251809@main)

    Web Animations

    • Added support for custom properties in Animation.commitStyles() (251858@main)
    • Added support for custom properties in JavaScript-originated animations (251856@main)

    Web Share


    • Added getPublicKey and getPublicKeyAlgorithm methods to AuthenticatorAttestationResponse (251844@main)
    • User handles that are too long or too short are now rejected (251938@main)
    • Empty RP ids are now rejected (252142@main)
    • Added support for authenticators over CCID (252425@main)
    • Fixed passing CBOR-encoded extensions along during assertions (252626@main)

    Web API

    • Designed an intentional way to invalidate platform font caches (251845@main)
    • Fixed PushManager in private browsing (251831@main)
    • Fixed IntersectionObserver stopping tracking when ⌘+ (Command-plus) is used to zoom in (251829@main)
    • Fixed ⇧⇥ (Shift-Tab) when focused in the middle of a contenteditable element (252029@main)
    • Fixed an issue where SameSite=Lax cookies were sometimes not sent on link navigations (252341@main)
    • Fixed SVGs loading an external file via <use> ignoring the ServiceWorker when offline (252132@main)
    • Fixed video.currentSrc to not be reset when a new load errors (251899@main)
    • Fixed an issue where inserting an iframe erroneously performs a micro-task checkpoint. (252015@main)


    • Fixed padding on a flex-based horizontal scroller preventing the last item from being fully viewable (251770@main)
    • Fixed clipping of absolutely positioned descendants of a transformed element with overflow: hidden (252387@main)
    • Implemented aspect-ratio mapping for the canvas element (252001@main)
    • Reduced memory use for for non-animated compositing layers with zero opacity (251965@main)
    • Fixed re-inserting <object> that previously rendered fallback content (251903@main)


    • Fixed WebSpeech to correctly allow the use of selected voices (252346@main)

    July 27, 2022 08:21 PM

    July 20, 2022

    New WebKit Features in Safari 15.6

    Surfin’ Safari

    Safari 15.6 is the seventh major release of Safari since last fall’s Safari 15.0.

    Continuing efforts from Safari 15.5, WebKit’s work for this version of Safari was focused predominately on fixing bugs and polishing existing features. Safari 15.6 does include one new feature for web developers — the :modal CSS pseudo-class.

    Safari 15.6 is available for macOS Monterey 12.5, macOS Big Sur, macOS Catalina, iPadOS 15.6, and iOS 15.6. You can update to Safari 15.6 on macOS Big Sur and macOS Catalina by going to System Preferences → Software Update → More info, and choosing to update Safari.

    Developer Features

    Newly defined in CSS Selectors level 4, the :modal pseudo-class can be used to select dialog elements opened with the showModal() API.

    Fixes and Polish

    And now, the list of bug fixes.


    • Fixed object-fit causing iframe contents to shift
    • Fixed inert behavior to apply to ::after, ::before, and ::marker pseudo-elements
    • Fixed :focus-visible matching a mouse click after a second element.focus() call
    • Fixed logical inline and logical block viewport units to be based on the current element’s writing-mode


    • Fixed resetting writing direction when inserting a new paragraph breaks out of a quoted reply block
    • Fixed managed pasteboard on iOS to work for all managed locations including managed Safari domains


    • Fixed page layout automatically when new fonts are installed


    • Fixed <link rel=preconnect> to respect crossorigin=anonymous and prevent sending credentials to different-origin links


    • Fixed blank video playback with only audio
    • Fixed replaceTrack with different constraints preventing sending packets
    • Fixed WebAudio playback rate speeding up for a few seconds when using createMediaElementSource
    • Fixed stalled playback when seeking in a xHE-AAC track backed by a SourceBuffer
    • Fixed starting audio playback in Safari when the device is locked
    • Fixed playback for video when the timeline sometimes goes backward at the start of playback
    • Fixed capturing and rendering audio simultaneously


    • Fixed flickering when rendering overlapping negative z-index layers
    • Fixed disappearing multi-column content inside a fixed position container when scrolling
    • Fixed images displayed in Reader not appearing in a print job or PDF export


    • Fixed missing text in a sign-in sheet when registering a passkey with syncing platform authenticator disabled if Touch ID is not setup

    Web Animations

    • Fixed computing implicit keyframes when a 0% and/or 100% keyframe is defined but only specifies a timing function

    Web API

    • Fixed communication between BroadcastChannel instances in distinct opaque origins

    Web Inspector

    • Fixed resources from memory cache showing empty content in Network, Sources, and Search tabs


    If you have bug fixes you would like to see addressed, or other ideas, including features you would like to see implemented in WebKit, file a WebKit bug report. Filing issues really does make a difference. We also welcome your feedback on the Safari user interface. And you can send a tweet to @webkit, @jensimmons or @jonathandavis to share your thoughts on this release. We love to hear from you.

    Download the latest Safari Technology Preview to stay at the forefront of the web platform and to use the very latest Web Inspector features.

    And More

    For more information, read the Safari 15.6 release notes.

    These features were first released in Safari Technology Preview: 146, 147, 148.

    July 20, 2022 05:25 PM

    Víctor Jáquez: Gamepad in WPEWebkit

    Igalia WebKit

    This is the brief story of the Gamepad implementation in WPEWebKit.

    It started with an early development done by Eugene Mutavchi (kudos!). Later, by the end of 2021, I retook those patches and dicussed them with my fellow igalian Adrián, and we decided to come with a slightly different approach.

    Before going into the details, let’s quickly review the WPE architecture:

    1. cog library — it’s a shell library that simplifies the task of writing a WPE browser from the scratch, by providing common functionality and helper APIs.
    2. WebKit library — that’s the web engine that, given an URI and other following inputs, returns, among other ouputs, graphic buffers with the page rendered.
    3. WPE library — it’s the API that bridges cog (1) (or whatever other browser application) and WebKit (2).
    4. WPE backend — it’s main duty is to provide graphic buffers to WebKit, buffers supported by the hardware, the operating system, windowing system, etc.

    Eugene’s implementation has code in WebKit (implementing the gamepad support for WPE port); code in WPE library with an API to communicate WebKit’s gamepad and WPE backend, which provided a custom implementation of gamepad, reading directly the event in the Linux device. Almost everything was there, but there were some issues:

    • WPE backend is mainly designed as a set of protocols, similar to Wayland, to deal with graphic buffers or audio buffers, but not for input events. Cog library is the place where input events are handled and injected to WebKit, such as keyboard.
    • The gamepad handling in a WPE backend was ad-hoc and low level, reading directly the events from Linux devices. This approach is problematic since there are plenty gamepads in the market and each has its own axis and buttons, so remapping them to the standard map is required. To overcome this issue and many others, there’s a GNOME library: libmanette, which is already used by WebKitGTK port.

    Today’s status of the gamepad support is that it works but it’s not yet fully upstreamed.

    • merged ">">libwpe pull request.
    • cog pull request — there are two implementations: none and libmanette. None is just a dummy implementation which will ignore any request for a gamepad provider; it’s provided if libmanette is not available or if available libwpe hasn’t gamepad support.
    • WebKit pull request.

    To prove you all that it works my exhibit A is this video, where I play asteroids in a RasberryPi 4 64 bits:

    The image was done with buildroot, using its master branch (from a week ago) with a bunch of modifications, such as adding libmanette, a kernel patch for my gamepad device, kernel 5.15.55 and its corresponding firmware, etc.

    By vjaquez at July 20, 2022 10:08 AM

    July 19, 2022

    Manuel Rego: Some highlights of the Web Engines Hackfest 2022

    Igalia WebKit

    Last month Igalia arranged a new edition of the Web Engines Hackfest in A Coruña (Galicia, Spain), where brought together more than 70 people working on the web platform during 2 days, with lots of discussions and conversations around different features.

    This was my first onsite event since “before times”, it was amazing seeing people for real after such a long time, meeting again some old colleagues and also a bunch of people for the first time. Being an organizer of the event meant that they were very busy days for me, but it looks like people were happy with the result and enjoyed the event quite a lot.

    This is a brief post about my personal highlights during the event.


    During the hackfest we had an afternoon with 5 talks, the talks were live streamed on YouTube and people could follow them remotely and also ask questions through the event matrix channel.

    Leo Balter's Talk

    Leo Balter’s Talk

    • Leo Balter talked about how Salesforce participates on the web platform as partner, working with browsers and web standards.
      I really liked this talk, because it explains how companies that use the web platform, can collaborate and have a direct impact on the evolution of the web. And there are many ways to do that: reporting bugs that affect your company, explaining use cases that are important for you and the things you miss from the platform, providing feedback about different features, looking for partners to fix outstanding issues or add support for new stuff, etc.
      Igalia has been showing during the last decade that there’s a way to have an impact on the web platform outside of the big companies and browser vendors. Thanks to our position on the different communities, we can help companies to push features they’re interested in and that would benefit the entire web platform in the future.
    • Dominik Röttsches gave a talk about COLRv1 fonts giving details on Chromium implementation and the different open-source software components involved.
      This new font format allows to do really amazing things and Dominik showed how to create a Galician emoji font with popular things like Tower of Hercules or Polbo á feira. With some early demos on variable COLRv1 and the beginnings of the first Galician emoji font…
    • Daniel Minor explained the work done in Gecko and SpiderMonkey to refactor the internationalization system.
      Very interesting talk with lots of information and details about internationalization, going deep on text segmentation and how it works on different languages, and also introducing the ICU4X project.
    • Ada Rose Cannon did a great introduction to WebXR and Augmented Reality.
      Despite not being onsite, this was an awesome talk and the video was actually a very immersive experience. Ada explained many concepts and features around WebXR and Augmented Reality with a bunch of cool examples and demos.
    • Thomas Steiner talked about Project Fugu APIs that have been implemented in Chromium.
      Using the Web Engines Hackfest logo as example, he explained different new capabilities that Project Fugu is adding to the web through a real application called SVGcode.

    It was a great set of talks, and you can now watch them all on YouTube. We hope you enjoy them if you haven’t the chance to watch them yet.

    CSS & Interop 2022

    On the CSS breakout session we talked about all the new big features that are arriving to browsers these days. Container Queries and :has being probably the most notable examples, features that people have been requesting since the early days and that are shipping into browsers this year.

    Apart from that, we talked about the Interop 2022 effort. How the target areas to improve interoperability are defined, and how much it implies the work in some of them.

    MathML & Fonts

    Frédéric Wang did a nice presentation about MathML and all the work that has been done in the recent years. The feature is close to shipping in Chromium (modulo finding some solution regarding printing or waiting for LayoutNG to be ready to print), that will be a huge step forward for MathML becoming it a feature supported in the major browser engines.

    Related to the MathML work there were some discussion around fonts, particularly OpenType MATH fonts, you can read Fred’s post for more details. There are some good news regarding this topic, new macOS version includes STIX Two Math installed by default, and there are ongoing conversations to get some OpenType MATH font by default in Android too.

    MathML Breakout Session MathML Breakout Session

    Accessibility & AOM

    Valerie Young, who has recently started acting as co-chair of the ARIA Working Group, was leading a session around accessibility where we talked about ARIA and related things like AOM.

    The Accessibility Object Model (AOM) is an effort that involves a lot of different things. In this session we talked about ARIA Attribute Reflection and the issues making accessible custom elements that use Shadow DOM, and that proposals like Cross-root ARIA Delegation are trying to solve.

    Accessibility Breakout Session Accessibility Breakout Session


    To close this post I’d like to say thank you to everyone that participated in the Web Engines Hackfest, without your presence this event wouldn’t make any sense. Also I’d like to thank the speakers for the great talks and the time devoted to work on them for this event. As usual big thanks to the sponsors Arm, Google and Igalia to make this event possible once more. And thanks again to Igalia for letting me be part of the event organization.

    Web Engines Hackfest 2022 Sponsors - Host & Organizer: Igalia. Gold Sponsors: Arm, Google and Igalia. Other Sponsors: Arm (Lunch sponsor) Web Engines Hackfest 2022 Sponsors

    July 19, 2022 10:00 PM

    July 15, 2022

    WPE WebKit Blog: WPE Graphics architecture

    Igalia WebKit

    Following the previous post in the series about WPE where we talked about the WPE components, this post will explain briefly the WPE graphics architecture, and how the engine is able to render HTML content into the display. If you haven’t read the previous entries in this blog post series about WPE, we recommend you to start with the first post in the series for an introduction, and then come back to this.

    DOM + CSS = RenderTree

    As the document is parsed, it will begin building the DOM tree and load-blocking CSS resources. At some point, possibly before the entire DOM tree is built, it’s time to draw things on the screen. The first step to render the content of a page is to perform what’s called the attachment, which is merging the DOM tree with the CSS rules, in order to create the RenderTree. This RenderTree is a collection of RenderObjects, structured into a tree, and each of these RenderObjects represent the elements in the DOM tree that have visual output. RenderObjects have the capability to render the associated DOM tree node into a surface by using the GraphicsContext class (in the case of WPE, this GraphicsContext uses Cairo to perform the rendering).

    Once the RenderTree is created, the layout is performed, ensuring that each of the RenderObjects have their proper size and position set.

    Going from source content to displayed content

    It would be possible to render the content of the web page just traversing this RenderTree and painting each of the RenderObjects, but there would be problems when rendering elements that overlap each other, because the order of the elements in the RenderTree doesn’t necessarily match the order in which they must be painted in order to get the appropriate result. For example, an element with a big z-index value should be painted last, no matter its position in the RenderTree.

    This is an example of how some HTML content is translated into the RenderTree (there are some RenderObjects missing here that are not relevant for the explanation).

    RenderTree generated from example HTML


    In order to ensure that the elements of the RenderTree are rendered in the appropriate order, the concept of RenderLayer is added. A RenderLayer represents a layer in the document containing some elements that have to be rendered at the same depth (even though this is not exactly the case, you can think of each RenderLayer as a group of RenderObjects that are at a certain z-index). Each RenderObject is associated to a RenderLayer either directly or indirectly via an ancestor RenderObject.

    RenderLayers are grouped into a tree, which is called the RenderLayer tree, and RenderLayer children are sorted into two lists: those that are below the RenderLayer, and those that are above. With this we have a structure that has grouped all the RenderObjects that have to be rendered together: they will be on top of the content that has has been rendered by the RenderLayers below this one, and and below the content rendered by the RenderLayers over this one.

    There are several conditions that can decide whether a RenderLayer is needed for some element, it doesn’t necessarily needs to be due to the usage of z-index. It can be required due to transparency, CSS filters, overflow, transformations, and so on.

    Continuing with the example, these are RenderLayers that we would get for that HTML code:

    RenderLayer tree generated from example HTML

    We can see that there are four RenderLayers:

    • The root one, corresponding to the RenderView element. This is mandatory.
    • Another one corresponding to the first RenderBlock.
    • One corresponding to the RenderVideo element, because video elements always get their own RenderLayer.
    • One corresponding to the transformed RenderBlock.

    RenderLayers have a paint method that is able to paint all the RenderObjects associated to the layer into a GraphicsContext (as mentioned, WPE uses Cairo for this). As in the previous case, it’s possible to paint the content of the page at this point just by traversing the RenderLayer tree and requesting the RenderLayers to paint their content, but in this case the result will be the correct one. Actually this is what WebKitGTK does when it’s run with accelerated compositing disabled.

    Layer composition

    While with the previous step we are already able to render the page contents, this approach is not very efficient, especially when the page contains animations, elements with transparency, etc. This is because in order to paint a single pixel, all the RenderLayers need to be traversed, and those that are contributing to that pixel need to be repainted (totally or partially), even if the content of those RenderLayers hasn’t changed. For example, think about an animation that’s moving an element. For each frame of that animation, the animated element needs to be repainted, but the area that was covered by the animated element in the last frame needs to be repainted as well. The same happens if there’s a translucent element on top of other content. If the translucent element changes, it needs to be repainted, but the content below the translucent element needs to be repainted as well because the blend needs to be performed again.

    This would be much more efficient if the content that doesn’t change was somehow separated from the content that’s changing, and we could render those two types of content separately. This is where the composition stage comes into action.

    The idea here is that we’re going to paint the RenderLayer contents into intermediate buffers, and then compose those buffers one on top of the other to get the final result. This last step is what we call composition. And it fixes the problems we mentioned with animations of transparency: animations don’t require repainting several RenderLayers. Actually moving an element just means painting one buffer with an offset during the composition. And for transparency, we just need to perform the new blending of the two buffers during the composition, but the RenderLayers of the content below the translucent element don’t need to be repainted.

    Once we have the RenderLayer tree, we could just paint each RenderLayer in its own buffer in order to perform the composition. But this would be a waste of memory, as not every RenderLayer needs a buffer. We introduce here a new component, the GraphicsLayer.

    GraphicsLayers are a structure used to group those RenderLayers that will render into the same buffer, and it will also contain all the information required to perform the composition of these buffers. A RenderLayer may have a GraphicsLayer associated to it if it requires its own buffer to render. Otherwise, it will render into an ancestor’s buffer (specifically, the first ancestor that has a GraphicsLayer). As usual, GraphicsLayers are structured into a tree.

    This is how the example code would be translated into GraphicsLayers.

    GraphicsLayer tree generated from example HTML

    We can see that we have now three GraphicsLayers:

    • The root one, which is mandatory. It belongs to the RenderView element, but the first RenderBlock will render into this GraphicsLayer's buffer as well.
    • The one for the RenderVideo element, as videos are updated independently from the rest of the content.
    • The one for the transformed element, as the transformed elements are updated independently from the rest of the content.

    Whith this structure, now we can render the intermediate buffers of the RenderView and the transformed RenderBlock, and we don’t need to update them any more. For each frame, those buffers will be composited together with the RenderVideo buffer. This RenderVideo will be updating its buffer whenever a new video frame arrives, but it won’t affect the content of the other GraphicsLayers.

    So now we have successfully separated the content that is changing and needs to be updated from the content that remains constant and doesn’t need to be repainted anymore, just composited.

    Accelerated compositing and threaded accelerated compositing

    There’s something else that be done in order to increase the rendering performance, and it’s using the GPU to perform the composition. The GPU is highly optimized to perform operations like the buffer composition that we need to do, as well as handle 3D transforms, blending, etc. We just need to upload the buffers into textures and let the GPU handle the required operations. WPE does this though the usage of the EGL and GLES2 graphics APIs. In order to perform the composition, EGL is used to create a GLES2 EGLContext. Using that context, the intermediate buffers are uploaded to textures, and then those textures are positioned and composited according to their appropriate positions. This leverages the GPU for the composition work, leaving the CPU free to perform other tasks.

    This is why this step is called accelerated compositing.

    But there’s more.

    Until this point, all the steps that are needed to render the content of the page are performed in the main thread. This means that while the main thread is rendering and compositing, it’s not able to perform other tasks, like run JS code.

    WPE improves this by using a parallel thread whose only mission is to perform the composition. You can think of it as a thread that runs a loop that composites the incoming buffers using the GPU when there’s content to render. This is what we call threaded accelerated compositing.

    This is specially useful when there’s a video or an animation running on the page:

    • If there’s a video running in the page, in the non-threaded case, for each video frame the main thread would need to get the frame and perform the composition with the rest of the page content. In the threaded case, the video element delivers the frames directly to the compositor thread, and requests a composition to be done, without the main thread being involved at all.

    • If there’s an animation, in the non-threaded case, for each frame of the animation the main thread would need to calculate the animation step and then perform the composition of the animated element with the rest of the page content. In the threaded case, the animation is passed to the compositor thread, and the animation steps are calculated on that thread, triggering a composition when needed. The main thread doesn’t need to to anything besides starting the animation.

    It would take another post to explain in detail how the threaded accelerated composition is implemented on WPE, but if you’re curious about it, know that WPE uses an specialization of the GraphicsLayer called CoordinatedGraphicsLayer in order to implement this. You can use that as an starting point.

    So this is the whole process that’s performed in WPE in order to display the content of a page. We hope it’s useful!


    At Igalia we’re constantly evolving and improving WPE, and have ongoing efforts to improve the graphics architecture as well. Besides small optimizations and refactors here and there, the most important goals that we have are:

    • Add a GPU process. Currently the EGL and GLES2 operations are performed in the web process. As there can be several web processes running when several pages are open, this means the browser can be using a lot of EGL contexts in total, which is a waste of memory. Also, all these processes could potentially be affected by errors, leaks, etc., in the code that handles the GPU operations. The idea is to centralize all the GPU operations into a single process, the GPU one, so all the web processes will issue paint requests to the GPU process instead of painting their content themselves. This will reduce the memory usage and improve the software’s robustness.

    • Remove CPU rasterization and paint all the content with GLES2. Using the CPU to paint the layer contents with cairo is expensive, especially in platforms with slow CPUs, as embedded devices sometimes do. Our goal here is to completely remove the cairo rasterization and use GLES2 calls to render the 2D primitives. This will greatly improve the rendering performance.

    • Use ANGLE to perform WebGL operations. WPE currently implements the WebGL 1.0 specification through direct calls to GLES2 methods. We are changing this in order to perform the operations using ANGLE, which will allow WPE to support the WebGL 2.0 specification as well.

    But what about the backends?

    In the previous post there was a mention of backends that are used to integrate with the underlying platform. How is this relevant to the graphics architecture?

    Backends have several missions when it comes to communicate with the platform, but regarding graphics, they have two functions to achieve:

    • Provide a platform dependent surface that WPE will render to. This can be a normal buffer, a Wayland buffer, a native window, or whatever, as long as the system EGL implementation allows creating an EGLContext to render to it.

    • Process WPE indications that a new frame has been rendered, performing whatever tasks are necessary to take that frame to the display. Also notify WPE when that frame was been displayed.

    The most common example of this is a Wayland backend, which provides a buffer to WPE for rendering. When WPE has finished rendering the content, it notifies the backend, which sends the buffer to the Wayland compositor, and notifies back to WPE when the frame has been displayed.

    So, whatever platform you want to run WPE on, you need to have a backend providing at least these capabilities.

    Final thoughts

    This was a brief overview of how WPE rendering works, and also what are the major improvements we’re trying to achieve at Igalia. We’re constantly putting in a lot of work to keep WPE the best web engine available for embedded devices.

    If this post got you interested in collaborating with WPE development, or you are in need of a web engine to run on your embedded device, feel free to contact us. We’ll be pleased to help!

    We also have open positions at the WebKit team at Igalia. If you’re motivated by this field and you’re interested in developing your career around it, you can apply here!

    July 15, 2022 12:00 AM

    July 14, 2022

    Release Notes for Safari Technology Preview 149

    Surfin’ Safari

    Safari Technology Preview Release 149 is available for macOS Monterey 12.3 or later and for macOS Ventura beta. Updates to Safari Technology Preview are no longer available for macOS Big Sur. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

    This release includes WebKit changes between:
    251460.7@safari-7614.1.17-branch…251769.19@safari-7614.1.19.1-branch (…d5f1f9386ecbb79d066e5b2d47d4e3cab0341464)

    Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

    Web Inspector

    • Elements Tab
      • Added support for forcing pseudo-class :focus-visible, :focus-within, and :target (251656@main, 251628@main)
      • Fixed empty space in the filter bar for the Fonts details sidebar (251704@main)
    • Extensions
      • Fixed Web Inspector window becoming inactive when an extension tab is selected (251645@main)


    • Added support for AVIF images on macOS Ventura and iOS 16 (251850@main)
    • Added support for emitting a resize event from PiP window (251512@main)
    • Fixed a black screen appearing in a muted video element outside the viewport (251596@main)
    • Fixed autoplay for an HTMLMediaElement created while page is interrupted (251691@main)
    • Fixed autoplay when moving a media element from one document to another (251595@main)
    • Fixed WebM to handle a case where a single video frame is appended (251485@main)
    • Fixed video.currentSrc to not get reset when a new load error occurs (251654@main)


    • Implemented the text-align-last property (251540@main)
    • Fixed styling of th elements when explicitly specifying text-align: inherit (251630@main)

    CSS Container Queries


    • Fixed repainting rect not taking into account text-underline-offset (251477@main)
    • Fixed incorrect sizing of elements with visually hidden text inside (251483@main)


    • Fixed Temporal regulateTime’s constraints for milliseconds, microseconds, and nanoseconds (behind --useTemporal flag) (251556@main, 251698@main)
    • Fixed Temporal.Instant#round to treat the Big Bang (not the Unix epoch) as zero (behind --useTemporal flag) (251556@main)
    • Fixed Temporal.PlainTime#since to handle ceil and floor properly (behind --useTemporal flag) (251556@main, 251555@main)

    Screen Sharing

    • Fixed resuming screen sharing after it is paused (251618@main)

    Web Animations

    • Added support for custom properties in @keyframes rules (251733@main)
    • Invalidated animation keyframes using container units on when container size changes (251574@main)


    • Fixed CTAP2_ERR_USER_ACTION_TIMEOUT handling (251511@main)
    • Renamed “cable” transport to “hybrid” (251621@main)
    • Fixed upgrading a legacy platform credential to a passkey does not delete the legacy credential (251646@main)

    Navigation Preload

    • Changed to not route the navigation preload response body to the service worker if it can be avoided (251493@main)

    Web API

    • Added support for waiting for event in custom element (251718@main)
    • Fixed scheduling or firing an event when selection changes for select() and setRangeText() (251716@main)
    • Implemented PerformanceResourceTiming.workerStart in ServiceWorkers (251523@main)
    • Switched Web Share Permissions Policy to * (251487@main)


    • Fixed CORS checks to not unblock cookies (251478@main)

    July 14, 2022 08:30 PM

    July 01, 2022

    Claudio Saavedra: Fri 2022/Jul/01

    Igalia WebKit

    I wrote a technical overview of the WebKit WPE project for the WPE WebKit blog, for those interested in WPE as a potential solution to the problem of browsers in embedded devices.

    This article begins a series of technical writeups on the architecture of WPE, and we hope to publish during the rest of the year further articles breaking down different components of WebKit, including graphics and other subsystems, that will surely be of great help for those interested in getting more familiar with WebKit and its internals.

    July 01, 2022 10:39 AM

    WPE WebKit Blog: An overview of the WPE WebKit project

    Igalia WebKit

    In the previous post in this series, we explained that WPE is a WebKit port optimized for embedded devices. In this post, we’ll dive into a more technical overview of the different components of WPE, WebKit, and how they all fit together. If you’re still wondering what a web engine is or how WPE came to be, we recommend you to go back to the first post in the series and then come back here.

    WebKit architecture in a nutshell

    To understand what makes WPE special, we first need to have a basic understanding of the architecture of WebKit itself, and how it ties together a given architecture/platform and a user-facing web browser.

    WebKit, the engine, is split into different components that encapsulate its different parts. At the heart of it is WebCore. As the name suggests, this contains the core features of the engine (rendering, layout, platform access, HTML and DOM support, the graphics layer, etc). However, some of these ultimately depend heavily on the OS and underlying software platform in order to function. For example: how do we actually do any I/O on different platforms? How do we render onscreen? What’s the underlying multimedia platform and how does it decode media and play it?

    WebCore handles the multitude of potential answers to these questions by abstracting the implementation of each component and allowing port developers to fill the gaps for each supported platforms. For example, for rendering on Mac, Cocoa APIs implement the graphics APIs needed. On Linux, this can be done through different implementations via Wayland, Vulkan, etc. For networking I/O on Mac, the networking APIs in the Foundation framework are used. On Linux, libsoup fills that gap, and so on.

    On the opposite side, for browser implementors to be able to write a browser using WebKit, an API is needed. WebKit, after all, is a library. WebKit ports, besides providing the platform support described above, also provide APIs that suit the target environments: The Apple ports provide Objective-C APIs (which are then used to write Safari and the iOS browsers, for instance), while the GTK+ port provides a GObject-based APIs for Linux (that are used in Epiphany, the GNOME browser, and other GNOME applications that rely on WebKit to render HTML). All of these APIs are built on top of an internal, middle-man, C API that is meant to make it easy for each port to provide a high-level API for browser developers.

    With all this in place, it would seem that it shouldn’t be so difficult for any vendor trying to reuse WebKit in a new platform to support new hardware and implement a browser, right? All that you need to do is:

    • Implement backends that integrate with your hardware platform: for multimedia, IO, OS support, networking, graphics, etc.
    • Write an API that you can use to plug the engine into your browser.
    • Maintain the changes needed off-tree, that is, outside the source code tree of WebKit.
    • Keep your implementation up-to-date with the many changes that happen in the WebKit codebase on a daily basis, so that you can update WebKit regularly and take advantage of the many bug fixes, improvements, and new features that land on WebKit continuously.

    Does that sound easy? No, it’s not easy at all! In fact, implementation of ports in this fashion is strongly discouraged and vendors who have tried this approach in the past have had to do a huge effort just to play catch-up with the fast-paced development of WebKit. This is where WPE comes to the rescue.

    Simplifying browsers development in the diverse embedded world

    To simplify the task of porting WebKit to different platforms, Igalia started working on a platform-agnostic, Linux-based, and full-featured port of WebKit. This port relies on existing and mature platform backends for everything that can be easily reused across platforms: multimedia, networking, and I/O, which are already present in-tree and are used by Linux ports, like the GTK one. For the areas that are most likely to require hardware-specific support (that is, graphics and input), WPE abstracts the implementation so that it can be more easily provided out of tree, allowing implementors to avoid having to deal with the WebKit internals more than what’s strictly needed.

    Additionally, WPE provides a high-level API that can be used to implement actual browsers. This API is very similar to the WebKitGTK API, making it easy for developers already familiar with the latter to start working with WPE. The cog library also serves as a wrapper around WPE to make it easier still. Once WPE was mature enough, it was accepted by Apple as an official WebKit port, meaning that the port lives now in-tree and takes immediate advantage of the many improvements that land on the WebKit repository on a daily basis.

    How does WPE integrate with WebKit?

    A diagram of the WPE WebKit architecture

    The WPE port has several components. Some are in-tree (that is, are a part of WebKit itself), while others are out-of-tree. Let’s examine those components and how they relate to each other, from top to bottom:

    • The Cog library. While not an integral part of WPE, libcog is a shell library that simplifies the task of writing a WPE browser from the scratch, by providing common functionality and helper APIs. This component also includes the cog browser, a simple WPE browser built on top of libcog that can be used as a reference or a starting point for the development of a new browser for a specific use case.
    • The WPE WebKit API: the entry point for browser developers to the WebKit engine, provides a comprehensive GObject/C API. The cog library uses this API extensively and we recommend relying on it, but for more specific needs and more fine-tuning of the engine, working directly with the WebKit API can be often necessary. The API is stable and easy to use, especially, and for those familiar with the GTK/GNOME platform.
    • WPE’s WebCore implementation: This part, internal to WebKit, implements an abstraction of the graphics and input layers of WebKit. This implementation relies on the libwpe library to provide the functionality required in an abstract way. Thanks to the architecture of WPE, implementors don’t need to bother with the complexities of WebCore and WebKit internals.
    • The libwpe library. This is an out-of-tree library that provides the API required by the WPE port in a generic way to implement the graphical and input backends. Specific functionality for a concrete platform is not provided, but the library relies on the existence of a backend implementation, as is described next.
    • Finally, a WPE backend implementation. This is where all the platform-specific code lives. Backends are loadable modules that can be chosen depending on the underlying hardware. These should provide access to graphics and input depending on the specific architecture, platform, and operating system requirements. As a reference, WPEBackend-fdo is a backend, which uses Wayland and technologies, and is supported for several architectures, including NXP and Broadcom chipsets, like the Raspberry Pi, and also regular PC architectures, easing testing and development.

    An implementor interested in building a browser in a new architecture only needs to focus on the development of the last component – a WPE backend. Having a backend, starting the development of a WebKit-powered browser is already much easier than it ever was!

    For a more detailed description of the architecture of WPE and WebKit, check this article on the architecture of WPE.

    OK, sounds interesting, how do I get my hands dirty?

    If you have made it this far, you should give WPE a try!

    We have listed several on the exploring WPE page. From there, you will see that depending on how interested you are in the project, your background, and what you’d like to do with it, there are different ways!

    It can be as easy as installing WPE directly from the most popular Linux distributions or downloading and flashing prebuilt images for the Raspberry Pi. There are easy and flexible options like Flatpak or Balena, which you can dig into to learn more. If you want to build WPE yourself, you can use Yocto and if you’d like to contribute—that’s very welcome!

    Happy hacking!

    July 01, 2022 12:00 AM

    June 29, 2022

    Release Notes for Safari Technology Preview 148

    Surfin’ Safari

    Safari Technology Preview Release 148 is now available for download for macOS Monterey 12.3 or later and macOS Ventura beta. Updates to Safari Technology Preview are no longer available for macOS Big Sur. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

    This release includes WebKit changes in WebKit-7614.1.17.2.

    Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

    Web Inspector

    • Added “Copy as fetch” in a contextual menu for resources (251226@main)
    • Updated Open Resource dialog to support matching file paths (251155@main)
    • Elements Tab
      • Added eyedropper to the color picker to allow picking a color from anywhere on the screen (251236@main)
      • Improved CSS autocompletion to suggest the most commonly used property, not the alphabetically first one (250994@main, 251171@main)
      • Layout
        • Fixed overlay color swatches to not allow format changes via context menu (251194@main)
        • Fixed overlay color swatches not updating the color of an overlay until the popover is dismissed (251248@main)
    • Sources Tab
      • Blackbox breakpoint evaluations in the debugger by default (251170@main)
      • Fixed newly added header not appearing when adding it to a request local override (250821@main)
      • Fixed the cursor not being automatically placed where it was before when creating a local override while viewing a resource (251279@main)
      • Fixed popover not resizing when wrapping to a new line while editing a breakpoint action (251072@main)
    • Network Tab
      • Added EventSource resource type (250672@main)
      • Fixed activity not getting marked as a previous session when the Network Tab is not selected (250902@main)
    • Timelines Tab
    • Audit Tab
      • Allowed audits to simulate a user gesture (251241@main)
      • Replaced the “Export Audit” and “Export Result” buttons with a single button that has a picker (251295@main)

    :has() pseudo-class

    • Added support for invalidating :target pseudo-class (250483@main)
    • Added partial support for invalidating :in-range and :out-of-range (250839@main)
    • Fixed invalidation with scope breaking :is() or :not() (251130@main)

    CSS Container Queries

    • Fixed a bug in style caching with container units (251268@main)
    • Changed to update query container layout unconditionally when an ancestor style changes (251310@main)


    • Added support for each-line keyword in text-indent (250978@main)
    • Changed to not allow unit-less values for CSS unprefixed perspective property (250582@main)
    • Fixed handling of text-align: match-parent on root element (251211@main)
    • Implemented :modal pseudo-class (250424@main)
    • Implemented ray() shape in offset-path (250437@main, 250776@main)
    • Implemented contain flag for ray() in offset-path (250776@main)
    • Updated logical *vi (inline) and *vb (block) viewport units to be based on the current element’s writing-mode (250835@main)


    • Made appearance: textfield behave like auto for most cases (250605@main)
    • Changed input.showPicker() to throw when the input element is readonly or disabled (250532@main)
    • Made input placeholder line-height user agent declaration !important (250414@main)
    • Made sure :active state is removed on keyup for radio button inputs (250734@main)
    • Removed the following values from the appearance property (250538@main, 250889@main, 250865@main, 250584@main, 250871@main)
      • caret
      • continuous-capacity-level-indicator
      • default-button
      • discrete-capacity-level-indicator
      • inner-spin-button
      • listitem
      • media-controls-dark-bar-background
      • media-controls-light-bar-background
      • progress-bar-value
      • rating-level-indicator
      • relevancy-level-indicator
      • sliderthumb-horizontal
      • sliderthumb-vertical


    • Changed to dynamically toggle acceleration of offset animations depending on the ability of animations in the effect stack to be accelerated (250737@main)
    • Ensured that animations using offset properties correctly run on a composited layer (250687@main)

    inert attribute

    • Fixed the inert attribute to ignore display: contents elements with assistive technologies (251185@main)
    • Fixed the inert attribute to affect pseudo-elements (250446@main)


    • Changed Temporal round and total methods to accept a string parameter (Temporal is behind the --useTemporal runtime flag) (250433@main)
    • Fixed Temporal.Duration#toString to never ignore fractionalSecondDigits (Temporal is behind the --useTemporal runtime flag) (250388@main)
    • Updated Temporal and Date to reject expanded year -000000 (Temporal is behind the --useTemporal runtime flag) (250432@main)


    • Fixed perspective to not be affected by transform-origin (250841@main)
    • Stopped assuming that an absolute positioned block box’s height is always resolvable (251215@main)

    Web API

    • Allowed Service Workers to run for a little longer in the case of functional events (250635@main)
    • Fixed fetch event handling delays caused by other JavaScript work on the main thread (251183@main)
    • Made sure calling showNotification will extend the service worker lifetime (250583@main)


    • Fixed sequential WebVTT cues with the same contents getting dropped (250988@main)
    • Fixed audio playback rate speeding up for few seconds when using createMediaElementSource (250579@main)
    • Fixed capturing a canvas that is not in the DOM leading to erratic frame rates or no frame emission at all (250996@main)
    • Changed to trigger element fullscreen video extraction after the seeked event (251090@main)


    • Added support for Link nonces (250972@main)
    • Aligned Intelligent Tracking Prevention client side cookie cap with other script-writable storage (251397@main)
    • Fixed script-src-elem Content Security Policies in workers (250386@main)


    • Fixed loading a very large image as an image document (251253@main)


    • Fixed automated mouse movement to correctly fire mouse events (251457@main)
    • Fixed rapid session creation and deletion leading to a timeout in session creation

    June 29, 2022 08:13 PM

    Patrick Griffis: WebExtension Support in Epiphany

    Igalia WebKit

    I’m excited to help bring WebExtensions to Epiphany (GNOME Web) thanks to investment from my employer Igalia. In this post, I’ll go over a summary of how extensions work and give details on what Epiphany supports.

    Web browsers have supported extensions in some form for decades. They allow the creation of features that would otherwise be part of a browser but can be authored and experimented with more easily. They’ve helped develop and popularize ideas like ad blocking, password management, and reader modes. Sometimes, as in very popular cases like these, browsers themselves then begin trying to apply lessons upstream.

    Toward universal support

    For most of this history, web extensions have used incompatible browser-specific APIs. This began to change in 2015 with Firefox adopting an API similar to Chrome’s. In 2020, Safari also followed suit. We now have the foundations of an ecosystem-wide solution.

    “The foundations of” is an important thing to understand: There are still plenty of existing extensions built with browser-specific APIs and this doesn’t magically make them all portable. It does, however, provide a way towards making portable extensions. In some cases, existing extensions might just need some porting. In other cases, they may utilize features that aren’t entirely universal yet (or, may never be).

    Bringing Extensions to Epiphany

    With version 43.alpha Epiphany users can begin to take advantage of some of the same powerful and portable extensions described above. Note that there are quite a few APIs that power this and with this release we’ve covered a meaningful segment of them but not all (details below). Over time our API coverage and interoperability will continue to grow.

    What WebExtensions can do: Technical Details

    At a high level, WebExtensions allow a private privileged web page to run in the browser. This is an invisible Background Page that has access to a browser JavaScript API. This API, given permission, can interact with browser tabs, cookies, downloads, bookmarks, and more.

    Along with the invisible background page, it gives a few options to show a UI to the user. One such method is a Browser Action which is shown as a button in the browser’s toolbar that can popup an HTML view for the user to interact with. Another is an Options Page dedicated to configuring the extension.

    Lastly, an extension can inject JavaScript directly into any website it has permissions to via Content Scripts. These scripts are given full access to the DOM of any web page they run in. However content scripts don’t have access to the majority of the browser API but, along with the above pages, it has the ability to send and receive custom JSON messages to all pages within an extension.

    Example usage

    For a real-world example, I use Bitwarden as my password manager which I’ll simplify how it roughly functions. Firstly there is a Background Page that does account management for your user. It has a Popup that the user can trigger to interface with your account, passwords, and options. Finally, it also injects Content Scripts into every website you open.

    The Content Script can detect all input fields and then wait for a message to autofill information into them. The Popup can request the details of the active tab and, upon you selecting an account, send a message to the Content Script to fill this information. This flow does function in Epiphany now but there are still some issues to iron out for Bitwarden.

    Epiphany’s current support

    Epiphany 43.alpha supports the basic structure described above. We are currently modeling our behavior after Firefox’s ManifestV2 API which includes compatibility with Chrome extensions where possible. Supporting ManifestV3 is planned alongside V2 in the future.

    As of today, we support the majority of:

    • alarms - Scheduling of events to trigger at specific dates or times.
    • commands - Keyboard shortcuts.
    • cookies - Management and querying of browser cookies.
    • downloads - Ability to start and manage downloads.
    • menus - Creation of context menu items.
    • notifications - Ability to show desktop notifications.
    • storage - Storage of extension private settings.
    • tabs - Control and monitoring of browser tabs, including creating, closing, etc.
    • windows - Control and monitoring of browser windows.

    A notable missing API is webRequest which is commonly used by blocking extensions such as uBlock Origin or Privacy Badger. I would like to implement this API at some point however it requires WebKitGTK improvements.

    For specific API details please see Epiphany’s documentation.

    What this means today is that users of Epiphany can write powerful extensions using a well-documented and commonly used format and API. What this does not mean is that most extensions for other browsers will just work out of the box, at least not yet. Cross-browser extensions are possible but they will have to only require the subset of APIs and behaviors Epiphany currently supports.

    How to install extensions

    This support is still considered experimental so do understand this may lead to crashes or other unwanted behavior. Also please report issues you find to Epiphany rather than to extensions.

    You can install the development release and test it like so:

    flatpak remote-add --if-not-exists gnome-nightly
    flatpak install gnome-nightly org.gnome.Epiphany.Devel
    flatpak run --command=gsettings org.gnome.Epiphany.Devel set org.gnome.Epiphany.web:/org/gnome/epiphany/web/ enable-webextensions true

    You will now see Extensions in Epiphany’s menu and if you run it from the terminal it will print out any message logged by extensions for debugging. You can download extensions most easily from Mozilla’s website.

    June 29, 2022 04:00 AM

    June 21, 2022

    Release Notes for Safari Technology Preview 147 with Safari 16 Features

    Surfin’ Safari

    Safari Technology Preview Release 147 is now available for download for macOS Monterey 12.3 or later and macOS Ventura beta. Updates to Safari Technology Preview are no longer available for macOS Big Sur. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS Monterey, or System Settings under General → Software Update on macOS Ventura.

    Note: Shared Tab Groups and syncing for Tab Groups, Website Settings, and Web Extensions are not enabled in this release.

    Many of the new Safari 16 features are now available in Safari Technology Preview 147:

    Live Text. Select and interact with text in videos or translate text in images on the web in macOS Ventura beta on Apple Silicon-based Macs.

    Web technologies. Experience and test the HTML, CSS, JavaScript, and other web technologies that are available in Safari 16 Beta and included in previous Safari Technology Preview releases.

    Web Push. Send notifications to people who opt-in on your website or web app with Safari Technology Preview on macOS Ventura beta.

    Passkeys. Preview the new type of phishing-resistant credential that makes signing in to websites safer and easier. Available through Safari’s WebAuthn platform authenticator. To learn more about passkeys, see Meet passkeys.

    Improved Safari Web Extensions. Test out API improvements including the ability to open a Safari Web Extension popover programmatically.

    Web Inspector Extensions. Build custom tooling or convert existing developer tools extensions to use in Web Inspector.

    Flexbox Inspector. Use the new visualization overlay in Web Inspector to help you more quickly and easily understand the layout of elements with Flexbox. It marks both the free space and gaps between flex items to reveal how they affect the result.

    If you see bugs or unexpected behavior with the interface of Safari Technology Preview, please file feedback with Apple’s Feedback Assistant. If you come across an implementation bug in web technology, Web Inspector, or have a request, please file a ticket on the WebKit bug tracker.

    June 21, 2022 08:40 PM

    June 20, 2022

    Frédéric Wang: Update on OpenType MATH fonts

    Igalia WebKit

    I mentioned in a previous post that Igalia organized the Web Engines Hackfest 2022 last week. As usual, fonts were one of the topic discussed. Dominik Röttsches presented COLRv1 color vector fonts in Chrome and OSS (transcript) and we also settled a breakout session on Tuesday morning. Because one issue raised was the availability of OpenType MATH fonts on operating systems, I believe it’s worth giving an update on the latest status…

    There are only a few fonts with an OpenType MATH table. Such fonts can be used for math layout e.g. modern TeX engines to render LaTeX, Microsoft Office to render OMML or Web engines to render MathML. Three of such fonts are interesting to consider, so I’m providing a quick overview together with screenshots generated by XeTeX from the LaTeX formula $${\sqrt{\sum_{n=1}^\infty {\frac{10}{n^4}}}} = {\int_0^\infty \frac{2x dx}{e^x-1}} = \frac{\pi^2}{3} \in {\mathbb R}$$:

    Recently, Igalia has been in touch with Myles C. Maxfield who has helped with internal discussion at Apple regarding inclusion of STIX Two Math in the list of fonts on macOS. Last week he came back to us announcing it’s now the case on all the betas of macOS 13 Ventura 🎉 ! I just tested it this morning and indeed STIX Two Math is now showing up as expected in the Font Book. Here is the rendering of the last slide of my hackfest presentation in Safari 16.0:

    Screenshot of a math formula rendered with STIX Two Math by Safari

    Obviously, this is a good news for Chromium and Firefox too. For the former, we are preparing our MathML intent-to-ship and having decent rendering on macOS by default is important. As for the latter, we could in the future finally get rid of hardcoded tables to support the deprecated STIXGeneral set.

    Another interesting platform to consider for Chromium is Android. Last week, there has been new activity on the Noto fonts bug and answers seem more encouraging now… So let’s hope we can get a proper math font on Android soon!

    Finally, I’m not exactly sure about the situation on Linux and it may be different for the various distributions. STIX and Latin Modern should generally be available as system packages that can be easily installed. It would be nicer if they were pre-installed by default though…

    June 20, 2022 12:00 AM

    June 07, 2022

    Meet Web Push

    Surfin’ Safari

    a push notification on macOS

    Websites have many reasons to notify their users of time-sensitive or high-priority events, even if the user does not currently have the site open. This feature is called Web Push, and is enabled by the W3C standards for Push API, Notifications API, and Service Workers, all working together. WebKit now supports the relevant parts of those standards to enable Web Push.

    Apple has made changes to macOS that deeply integrate with WebKit’s support to provide a great user experience, and we’re excited to announce that Web Push is supported in Safari 16 on macOS Ventura.

    Keep an eye out for Web Push on iOS and iPadOS in 2023.

    As long as you’ve coded your web application to the standards you will be able to reach Safari 16 users on macOS Ventura. You don’t need to join the Apple Developer Program to send Web Push notifications.

    If you exclude Safari through browser detection, now would be a great time to switch to feature detection, which lets you take advantage of new features as soon as they’re supported. Additionally, if you tightly manage push end points on your server, be sure to allow URLs from any subdomain of

    All of this and more is covered in Meet Web Push (15 minute video) at WWDC22.

    Standards overview

    Most features of the web platform are described in a single web standard. Web Push is an exception, with multiple standards describing implementation requirements.

    There are many resources on the web to help web application authors get up and running with these standards. But to further cover how WebKit’s support works, it is useful to cover the web standards at a high level.

    The Push API standard is the most directly relevant to start with. It describes the JavaScript interface that allows a website to register a push subscription. That subscription enables sending push messages to your user’s browser using a push service.

    The ServiceWorker API is extended to support these push messages. Once a push message is received from a domain, that domain’s registered service worker script receives an event representing the push message.

    The Notifications API is extended to allow service worker scripts to post a notification even without an open browser tab.

    When a web application registers a push subscription, they promise that pushes will always be user visible. When the service worker handles a push message, it is required to use the Notifications API to display a user visible notification. Finally, when the user activates that notification, the service worker is sent an event representing the notification activation.

    Power and privacy

    Both the WebKit open source project and Apple treat privacy as a fundamental human right. As with other privileged features of the web platform, requesting a push subscription requires an explicit user gesture. It also requires you set the userVisibleOnly flag to true, and fulfill that promise by always showing a notification in response to a push message.

    The Web Push API is not an invitation for silent background runtime, as that would both violate a user’s trust and impact a user’s battery life.

    Violations of the userVisibleOnly promise will result in a push subscription being revoked.

    A little bit about WebKit

    Some of you are interested in the implementation details of Web Push in WebKit.

    One goal of the WebKit open source project is to make it easy to deliver a modern browser engine that integrates well with any modern platform.

    Many web-facing features are implemented entirely within WebKit, and the maintainers of a given WebKit port do not have to do any additional work to add support on their platforms.

    Occasionally features require relatively deep integration with a platform. That means a WebKit port needs to write a lot of custom code inside WebKit or integrate with platform specific libraries. For example, to support the HTML <audio> and <video> elements, Apple’s port leverages Apple’s Core Media framework, whereas the GTK port uses the GStreamer project.

    A feature might also require deep enough customization on a per-Application basis that WebKit can’t do the work itself.

    For example web content might call window.alert(). In a general purpose web browser like Safari, the browser wants to control the presentation of the alert itself. But an e-book reader that displays web content might want to suppress alerts altogether.

    From WebKit’s perspective, supporting Web Push requires deep per-platform and per-application customization.

    Web Push in Apple’s WebKit port

    Apple’s WebKit port includes a new daemon called webpushd. It is installed as a LaunchAgent in macOS Ventura to support Web Push. This daemon takes push subscription requests from webpages in Safari 16 and turns them into actual push subscriptions with the Apple Push Notification service.

    Incoming pushes to the system are delivered to webpushd, which then wakes the appropriate application to hand off any pending push messages to a service worker.

    The promise of Web Push is that you can reach your users even if they don’t have your website open in a browser tab. Because of how we integrated webpushd with built-in push support in macOS Ventura, Safari doesn’t even need to be running for a push message to be delivered.

    The requirement to display user visible notifications is another platform specific point. Different browsers might implement Notifications API support in different ways. Safari has always supported local notifications by relying on the macOS Notification Center and has made additional changes to handle activating these notifications when Safari is not running.

    Integrating Apple Push Notification service’s new Web Push support with webpushd and supporting notifications while Safari isn’t running are both system-level changes, making our implementation require macOS Ventura and later.

    More resources

    Apple has a few more resources to learn more about Web Push support in Safari 16 on macOS Ventura:

    MDN has some great resources on Web Push. You should start out with Web Push API Notifications best practices.

    And of course you can always reference the W3C standards directly:

    June 07, 2022 03:00 PM

    June 06, 2022

    Web technology sessions at WWDC22

    Surfin’ Safari

    WWDC22 is here, and with it, a host of announcements of new web technology shipping in WebKit on macOS, iOS and iPadOS, including advancements in privacy and security – plus new features for Safari, Web Inspector and Safari Web Extensions. Much of the news was announced on Monday during this year’s keynote, is listed in the Safari 16 Beta Release Notes, and is described in News from WWDC: WebKit Features in Safari 16 Beta. But that’s not all.

    Ten sessions at WWDC22 go into greater detail, demonstrating new technology directly relevant to web developers. New videos will be released each day this week. You can watch them on the WWDC22 website, or in the Apple Developer app for macOS, iOS, iPadOS, and tvOS.

    Be part of the conversation during WWDC on the Apple Developer Forums, or share your thoughts with @WebKit on Twitter.

    Tuesday, June 7

    What’s new in Safari and WebKit

    Explore the latest features in Safari and WebKit and learn how you can make better and more powerful websites. We’ll take you on a tour through the latest updates to HTML, CSS enhancements, Web Inspector tooling, Web APIs, and more.

    Watch What’s new in Safari and WebKit starting on Tuesday, June 7.

    Meet Web Push for Safari

    Bring better notifications to your websites and web apps in Safari on macOS with Web Push. We’ll show you how you can remotely send notifications to people through the web standards-based combination of Push API, Notifications API, and Service Workers.

    Watch Meet Web Push for Safari starting on Tuesday, June 7.

    Meet Passkeys

    It’s time for a security upgrade: Learn how to add support for passkeys to create a quick and easy sign in experience for people, all while offering a radical increase to account security. Passkeys are simple and strong credentials built to eliminate phishing attacks. We’ll share how passkeys are designed with security in mind, show you how people will use them, go over how to integrate passkeys in your log in flow, and explore the platform and web APIs you need to adopt this feature.

    Watch Meet Passkeys starting on Tuesday, June 7.

    Wednesday, June 8

    What’s new in Safari Web Extensions

    Learn how you can use the latest improvements to Safari Web Extensions to create even better experiences for people browsing the web. We’ll show you how to upgrade to manifest version 3, adopt the latest APIs for Web Extensions, and sync extensions across devices.

    Watch What’s new in Safari Web Extensions starting on Wednesday, June 8.

    Replace CAPTCHAs with Private Access Tokens

    Don’t be captured by CAPTCHAs! Private Access Tokens are a powerful alternative that help you identify HTTP requests from legitimate devices and people without compromising their identity or personal information. We’ll show you how your app and server can take advantage of this tool to add confidence to your online transactions and preserve privacy.

    Watch Replace CAPTCHAs with Private Access Tokens starting on Wednesday, June 8.

    Thursday, June 9

    Create Safari Web Inspector Extensions

    Learn how to add your own tools directly into Web Inspector using the latest Web Extensions APIs. We’ll show you how to create your own tab in Web Inspector, evaluate JavaScript in the inspected page, and use the result to help you troubleshoot and identify potential problems.

    Watch Create Safari Web Inspector Extensions starting on Thursday, June 9.

    What’s new in web accessibility

    Discover techniques for building rich, accessible web apps with custom controls, SSML, and the dialog element. We’ll discuss different assistive technologies and help you learn how to use them when testing the accessibility of your web apps.

    Watch What’s new in web accessibility starting on Thursday, June 9.

    Enhance your Sign in with Apple experience

    Learn how you can provide safe and fast authentication in your app using Sign in with Apple. We’ll show you how you can upgrade password-based accounts into secure, single-tap login credentials, and explore how you can seamlessly handle changes to user sessions in your app. We’ll also help you take advantage of Sign In with Apple across the web and on other platforms. To get the most out of this session, we recommend having familiarity with Sign In with Apple and REST API. We’d also recommend having a basic understanding of JavaScript.

    Watch Enhance your Sign in with Apple experience starting on Thursday, June 9.

    Friday, June 10

    What’s new in WKWebView

    Explore the latest updates to WKWebView, our framework for incorporating web content into your app’s interface. We’ll show you how to use the JavaScript fullscreen API, explore CSS viewport units, and learn more about find interactions. We’ll also take you through refinements to content blocking controls, embedding encrypted media, and using the Web Inspector.

    Watch What’s new in WKWebView starting on Friday, June 10.

    Improve DNS security for apps and servers

    Discover the latest ways to ensure that DNS — the foundation of internet addressing — is secure within your app. Learn how to authenticate DNS responses in your app with DNSSEC and enable DNS encryption automatically with Discovery of Designated Resolvers (DDR).

    Watch Improve DNS security for apps and servers starting on Friday, June 10.

    June 06, 2022 09:30 PM

    News from WWDC22: WebKit Features in Safari 16 Beta

    Surfin’ Safari

    WebKit has had a big year, with over 162 new features and improvements shipping in WebKit browsers — including Safari 15.2, Safari 15.4, and Safari 15.5. Features from earlier this year include dialog element, lazy loading, inert, :has() pseudo-class, new viewport units, Cascade Layers, focus visible, accent color, appearance, font palettes for color fonts, BroadcastChannel, Web Locks API, File System Access API, enhancements to WebAssembly, support for Display-P3 in canvas, additions to COOP and COEP, improved CSS autocompletion and new CSS variable tooling in Web Inspector, and much, much more.

    We’re excited to announce today the major web technologies shipping in Safari 16 beta.

    You can try out Safari 16 on macOS Monterey or macOS Big Sur by downloading the Safari 16.0 public beta. You will need to sign in using a free Apple ID to download. Note that installing Safari 16 beta will replace your existing Safari install with no way to revert to an earlier version.

    Or, if you’d like, you can test Safari 16 by installing the public beta of macOS Ventura, iOS 16, or iPadOS 16.

    Web Inspector Extensions

    Safari 16 brings support for Web Inspector Extensions, so you can enhance Safari’s built-in browser developer tools. This can be especially helpful when using powerful third-party frameworks and services — perhaps your team uses React, Angular, Vue, or Ember; or maybe a popular test suite or another developer service. Now with Safari Web Inspector Extensions, you’ll be able install developer tools extensions from those frameworks and services to make your job developing with them faster and easier. Look for such extensions in the App Store this fall.

    Extensions for popular third-party frameworks and services aren’t the only exciting use of Web Inspector Extensions. Often, a small enhancement to developer tools can make a huge difference in workflow. You might be the best person to imagine and create such an extension. Web extensions are made from HTML, CSS, and JS — a perfect project for web developers. To learn the basics of building a Safari Web Extension, either from a quick-start template or by converting an existing extension to work with Safari, along with how to package it for the App Store, watch the Tech Talk Build and deploy Safari Extensions.

    Safari Web Inspector Extensions are made with the same JavaScript APIs as the developer tools extensions in other browsers. This makes it possible for the creators of your favorite developer tools extensions to easily port them to Safari.

    Web Inspector Extensions join other improvements to Safari Web Extensions, including the ability to sync which extensions are enabled across iOS, iPadOS, and macOS.

    Container Queries

    After years of collaboration by engineers working on various browsers to figure out whether or not they would even be possible, Container Queries are finally here. Similar to Media Queries, Container Queries allow you to adjust the layout or styling of a particular item on your web page based on the size of its container rather than the size of the viewport. They’ll be an invaluable tool for creating reusable components in a design system.

    Safari 16 supports size queries and container query units. “Size queries” are what web developers imagine when they talk about container queries — the opportunity to write CSS that only applies if a container is a certain size. Other ideas for style queries are also being discussed as part of Container Queries as something for the future.

    Container query units are similar to viewport units, but they specify a length relative to the dimensions of a query container instead of the viewport.

    unit relative to
    cqw 1% of a query container’s width
    cqh 1% of a query container’s height
    cqi 1% of a query container’s inline size
    cqb 1% of a query container’s block size
    cqmin The smaller value of cqi or cqb
    cqmax The larger value of cqi or cqb

    Web Push for macOS

    a push notification on macOS

    Web Push is coming to Safari 16 on macOS Ventura. This lets you remotely send notifications to users of your websites and web apps — and deliver those notifications even when Safari isn’t running. It uses the same combination of web standards you may be familiar with from other browsers: Push API and Notifications API, along with Service Worker.

    Users opt into notifications by first indicating interest through a user gesture — such as clicking a button. Then, they’ll be prompted to give permission for your site or app to send notifications. Users will be able to view and manage notifications in Notifications Center, and customize styles and turn notifications off per website in Notifications Settings.

    If you’ve already implemented Web Push for your web app or website using industry best practices, it will automatically work in Safari. Although, if you’ve excluded Safari through browser detection, you’ll need to switch to feature detection to get it working.

    Web Push in Safari uses the same Apple Push Notification service that powers native push on all Macs and iOS devices. If you tightly manage push endpoints on your server, be sure you allow URLs from any subdomain of You do not need to be an Apple Developer Program member.

    And look for Web Push for iOS and iPadOS in 2023.


    CSS Grid shipped over five years ago, in March 2017, revolutionizing what’s possible in layout design on the web. Subgrid takes Grid to another level, providing an easy way to put grandchildren of a grid container on that grid. It makes it possible to line up items across complex layouts without being constrained by the HTML structure. And Safari’s Grid Inspector lets you turn on the overlays for as many grids as you want — which is especially helpful when coding subgrid.

    Flexbox Inspector

    Following last year’s Grid Inspector, Safari 16 adds a Flexbox Inspector. It pairs perfectly with the addition of the Alignment Editor in Safari 15.4.

    Overlays for Flexbox containers make it easier to visualize the effects your CSS has on Flexbox containers. The new overlay helps you visually distinguish between free space and gaps. It also shows the bounds of items revealing how they are distributed both on the main axis and cross axis of your Flexbox containers. The toggle-able “Order Numbers” option helps show the layout order of elements in the container, which can be helpful when using the order CSS property for items. And, just like our overlays for Grid last year, you can turn on as many Flexbox overlays as you need, without impacting performance.

    Accessibility Improvements

    Safari 16 introduces a re-architecture of WebKit’s accessibility support on macOS that delivers improved performance and increased responsiveness. This change allows WebKit to service more accessibility requests from clients like VoiceOver in less time than before. On some complex webpages, we’ve measured twice the number of accessibility requests served in twenty-five percent less time.

    This release also greatly improves accessibility support for elements with display:contents by ensuring they are properly represented in the accessibility tree.

    Animation Improvements

    CSS Offset Path (also known as Motion Path) provides web developers a way to animate things along a custom path of any shape. The offset-path property let’s you define a geometrical path along which to animate. The offset-anchor, offset-distance, offset-position, and offset-rotate properties give you additional abilities to refine the exact movement of the object being animated. While the offset property acts as a shorthand for combining these properties.

    With Safari 16, you can now animate a CSS Grid. That means changes in the size of rows and/or columns can be animated, opening up a whole new set of possibilities for movement on a page.

    Safari 16 also adds support for composite operations, resolving how an element’s animation impacts its underlying property values. And it adds support for discrete animation to thirty-nine CSS properties — see the full list in the Safari Technology Preview 143 release notes.

    Overscroll Behavior

    CSS Overscroll Behavior determines what happens when a user scrolls and reaches the boundary of a scrolling area. It’s useful when you want to stop scroll chaining — when a user scrolls inside a box and hits the end, you now have control over stopping or allowing scrolling on the rest of the page.

    Shared Worker

    Just when you thought there weren’t enough different kinds of workers, there’s a new type of worker in Safari — Shared Worker. Like Service Worker, a Shared Worker runs JavaScript in the background, but its lifetime is slightly different. Your Shared Worker runs as long as the user has any tab open to your domain, and all the tabs open to the same domain can share the same Shared Worker. So, if you want to do something like have one WebSocket connection open to a server that communicates on behalf of multiple tabs, try out Shared Worker.

    And more

    There’s much more, including fixes and improvements to form controls as well as support for <form>.requestSubmit() and the showPicker() method for HTML input elements. Plus support for Shadow Realms, as well as support for the worker-src Content Security Policy directive.

    To learn more about what’s in Safari 16 for web developers, including a list of bug fixes, read the Safari 16 beta release notes.


    We love hearing from you. Send a tweet to @webkit, @jensimmons, or @jonathandavis to share your thoughts on this release. What technology from Safari 16 are you most excited about? What features or fixes do you want to see next? If you run into any issues, we welcome your feedback on Safari UI, or your WebKit bug report about web technology or Web Inspector. Filing issues really does make a difference.

    Download the latest Safari Technology Preview to stay at the forefront of the web platform and to use the latest Web Inspector features. You can also use the WebKit Feature Status page to watch for new information about the web features that interest you the most.

    June 06, 2022 07:00 PM

    May 31, 2022

    Happy birthday, WPE WebKit!

    Surfin’ Safari

    WebKit is the purring engine of Safari, it’s true, but it has numerous ports and many contributors. These ports are used for all sorts of things, from powering Sony PlayStations to driving millions of embedded devices all over the world. Embedded use cases like smart home appliances, digital signage, and automotive displays are largely possible thanks to WPE WebKit, the official port of WebKit specifically optimized for embedded devices. That port is maintained by Igalia, an open-source consultancy headquartered in Spain. Their work on WPE WebKit goes a long way toward explaining why Igalia is the most prolific external contributor to the WebKit codebase, accounting for almost 17% of all commits in 2021.

    Igalia recently celebrated the fifth birthday of WPE WebKit with a blog post on their WPE web site, covering its evolution from a fork of WebKitGTK to a Wayland-based renderer to a framework compatible with almost any rendering backend before its public launch on 21 April 2017. They also promise a series of articles to come profiling the people who work on WPE WebKit and talking about some of the technical aspects of advancing such a project. You can read more about it in their post. Happy 5th birthday, WPE!

    May 31, 2022 04:00 PM

    May 25, 2022

    Release Notes for Safari Technology Preview 146

    Surfin’ Safari

    Safari Technology Preview Release 146 is now available for download for macOS Big Sur and of macOS Monterey 12.3 or later. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 293023-293745.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Elements Tab
      • Fixed hovering over a node in the Layout panel to now highlight it on the page (r293189)
      • Fixed <button> and <select> elements appearing in the list of Flex containers (r293565)
    • Sources Tab
      • Added ability for local overrides to entirely block a request (r293409)
    • Timelines Tab
      • Fixed importing a timeline leaves the overview non-scrollable/non-zoomable until windows is resized (r293727)
    • Graphics Tab
      • Improved display of GLenums and GLbitfield in WebGL canvas recordings (r293541, r293706)


    • Fixed ::first-letter when used in shadow content (r293497)
    • Fixed revert-layer in shadow tree contexts (r293725)
    • Fixed cascade rollback for deferred properties (r293485)
    • Related properties sharing a computed value (r293602)
    • Made word-wrap CSS property an alias of overflow-wrap (r293521)
    • Made -webkit-transform-style an alias of transform-style (r293524)
    • Unprefixed the -webkit-user-select CSS property (r293089)
    • Removed some unimplemented -webkit-appearance keywords (r293511)
    • Updated the user-agent stylesheet to include table { text-indent: initial } to conform with the HTML standard (r293322)


    • Added ISO8601 based Temporal.PlainDate getters behind a flag (r293708)
    • Enabled change-array-by-copy (r293348)
    • Fixed WASM to throw consistent exceptions for memory.init and memory.copy (r293252)
    • Fixed JS stack traces to report the correct column number in CR-LF line ending style HTML files (r293672)


    • Fixed setting the correct selection range for textarea when updating the default value (r293673)
    • Fixed constructed FormData object to not contain an entry for the submit button that was used to submit the form (r293444)
    • Fixed user-select: none to have no effect on editability (r293028)


    • Fixed the media controls overflow button flickering sometimes (r293658)
    • Fixed HTMLMediaElement getting multiple interruptions for invisible autoplay (r293609)
    • Fixed MediaSession.setPositionState() (r293488)


    • Fixed the quirk to only stretch the percent height body when it is the document element’s child (r293647)
    • Made contain: layout on the html element change position: fixed behavior (r293209)


    • Fixed smooth scrolling behavior when focusing a scroll container before beginning to scroll (r293260)


    • Fixed <link rel=preconnect> always sending credentials to a different origin, ignoring crossorigin=anonymous (r293503)

    Shared Worker

    • Fixed resuming a suspended remote shared worker when a new SharedWorker object is created (r293173)

    Service Worker

    • Fixed Service Worker loads to not expose some ResourceTiming information (r293418)
    • Fixed Service Worker update to refresh imported scripts in addition to the main script (r293506)
    • Fixed Service Worker to not intercept embed- or object-related loads (r293417)
    • Fixed ServiceWorkerRegistration updates to fail if called from an installing Service Worker context (r293719)
    • Fixed URL.createObjectURL to not be exposed in Service Worker contexts (r293717)

    Web API

    • Fixed Web Locks held in a Worker not getting released on page refresh or exit (r293329)


    • Changed to not expose ARIA roleDescription value on “generic” elements (e.g. div and span) unless an explicit role value is also defined (r293345)


    • Fixed mixing strict-dynamic and unsafe-inline Content Security Policies (r293603)
    • Set top origin of CORS preflight requests (r293591)

    May 25, 2022 08:43 PM

    Customizing Color Fonts on the Web

    Surfin’ Safari

    Color fonts provide a way to add richness to your designs without sacrificing any of the many benefits of using plain text. Regardless of how decorative a color font is, the underlying text is always searchable, copy/paste-able, scalable, translatable, and compatible with screen readers.

    WebKit now supports CSS @font-palette-values. With this at-rule, you can access predefined color palettes provided by the font designer, and you can customize them in order to make the color font a perfect match for the colors in your designs.

    ONCE upon a time in the middle of winter, when the flakes of snow were falling like feathers from the clouds, a Queen sat at her palace window, which had an ebony black frame, stitching her husband’s shirts. While she was thus engaged and looking out at the snow she pricked her finger, and three drops of blood fell upon the snow. Now the red looked so well upon the white that she thought to herself, “Oh, that I had a child as white as this snow, as red as this blood, and as black as the wood of this frame!” Soon afterwards a little daughter came to her, who was as white as snow, and with cheeks as red as blood, and with hair as black as ebony, and from this she was named “Snow-White.” And at the same time her mother died.

    THIS answer so angered the Queen that she became quite yellow with envy. From that hour, whenever she saw Snow-White, her heart was hardened against her, and she hated the little girl. Her envy and jealousy increased so that she had no rest day or night, and she said to a Huntsman, “Take the child away into the forest. I will never look upon her again. You must kill her, and bring me her heart and tongue for a token.” The Huntsman listened and took the maiden away, but when he drew out his knife to kill her, she began to cry, saying, “Ah, dear Huntsman, give me my life! I will run into the wild forest, and never come home again.”

    You can try out @font-palette-values today in Safari 15.4 or later.

    Color palettes work great in WebKit with the COLRv0 font file format, and we are investigating other formats like SVG.

    Background on the font above

    The font in this demo is a revival of Bradley, a “fairytale blackletter” originally released in 1895. The typeface came with a special set of ornate Initial caps meant for drop caps (see also ::initial-letter) and other titling uses, which David digitized this past December for his Font of the Month Club, just in time for the holidays.

    Each glyph is made up of a handful of distinct layers (letterform, backdrop, ornate linework, letter outline, and border). Making the layers different-yet-coordinated colors adds depth to the design, taking it beyond what a simple foreground/background can provide. It felt like the perfect use case for a color font with multiple color palettes, and a unique opportunity for a 127-year-old font to play a small part in an emerging font technology.

    Palettes in CSS

    Fonts can define one or more of their own color palettes inside the CPAL table inside the font file. The palettes are ordered, and so they are identified by index. For example, a font might define color palette #3 that uses blues and greens, but another palette #5 might use reds and oranges. Colors within a palette inside the font are also identified by index – all palettes contain the same number of colors within themselves.

    These color palettes can be tweaked or overridden in CSS using font-palette-values. An example looks like this:

    @font-palette-values --lilac-blossom {
        font-family: "Bradley Initials DJR Web";
        base-palette: 7;
        override-colors: 0 #fff, 1 #F3B0EB;

    This example means “Make a color palette named Lilac Blossom, that, when applied to Bradley Initials DJR Web, is just like the 7th palette in the font, but overrides color #0 in that palette to be white, and color #1 in the palette to be #F3B0EB.” If you don’t want to override any colors, that’s no problem – just delete the entire override-colors descriptor.

    You can then apply this color palette by simply supplying it to the font-palette property like this:

    font-palette: --lilac-blossom;
    The Round Table


    Progressive Enhancement

    If you’re using an older version of Safari, or a different browser which doesn’t understand the font-palette property, it will render the default (0th) color palette in the font. Here’s what the above example would look like in such a browser:

    The Round Table


    If the fallback behavior is undesirable for your particular font, you can detect browsers that understand CSS color palettes in your stylesheet by using the @supports media query, like so:

    @supports (font-palette: --lilac-blossom) {
        .lilacblossom {
            font-palette: --lilac-blossom;
    @font-palette-values --lilac-blossom {
        font-family: "Bradley Initials DJR Web";
        base-palette: 7;
        override-colors: 0 #fff, 1 #F3B0EB;

    Dark Mode

    Not all color palettes are clearly visible on all backgrounds. Without color fonts, the color used to render text was entirely determined by the CSS author, but now that fonts can have color palettes defined inside them, it’s up to CSS authors to pick or create a color palette that is legible on the background it’s being rendered on. This can be particularly tricky when font fallback occurs, or when the user has blocked some fonts from loading.

    Fonts such as Bradley Initials DJR Web have an extra tool for helping with this, though. Fonts can indicate that certain palettes inside them are usable with light backgrounds or usable with dark backgrounds, and these palettes are hooked up to the font-palette property. You don’t even have to use @font-palette-values!

    So, if you want to use a color palette on a dark background, you can simply say font-palette: dark, like this:


    And the same thing for a light background: font-palette: light:


    Because the font-palette property has no effect on non-color fonts, it’s safe to set it in conjunction with the prefers-color-scheme media query, like this:

    @media (prefers-color-scheme: dark) {
        :root {
            background: black;
            color: white;
            font-palette: dark;


    Because @font-palette-values blocks are scoped to a specific font, you can make multiple of them that share a name. This is really powerful – it means you can define a single color palette name, and have it applied differently to whatever font happens to be rendered with it. Here’s an example:

    @font-palette-values --lilac-blossom {
        font-family: "Bradley Initials DJR Web";
        base-palette: 1;
    @font-palette-values --lilac-blossom {
        font-family: "Megabase";
        base-palette: 2;
    <div style="font-palette: --lilac-blossom;">
        <div style="font-family: 'Bradley Initials DJR Web';">Pizza is amazing!</div>
        <div style="font-family: 'Megabase Web';">Is there any food better than pizza?</div>

    This will have Bradley Initials DJR Web’s Lilac Blossom palette applied to Bradley Initials DJR Web, and Megabase’s Lilac Blossom palette applied to Megabase. And you only had to specify the font-palette property once!

    Contextual color

    In addition to pulling colors from palettes, some color fonts set aside special shapes that are connected to the foreground color of the current element. This makes them extra flexible, but it also means that these shapes operate independently from font-palette. In these cases, you can simply use the color property to change their color, like in this demo using Megabase.

    <div style="font-family: 'Megabase Web';">
        <div style="color: black;">They were just pushed into space.</div>
        <div style="color: blue;">As much as I care about you.</div>

    They were just pushed into space.

    As much as I care about you.


    Of course, with power comes responsibility; just because you can change colors, doesn’t mean you always should. Often the colors in a palette are coordinated to harmonize aesthetically, so it’s good to have a sense of how they are meant to relate to one another. You can look at the font’s predefined color palettes to see how the font designer assigned the roles for each color in the palette, and tweak accordingly.

    It is also important to choose colors that contrast strongly against the background in order to keep your text readable and your webpage accessible. Colors in the palette that are used to form the base of the letters should typically “pop” against the background, while supporting layers like shadows, outlines, and decorative elements might contrast less in order to keep them from overpowering the letterforms.

    Color fonts are a great improvement over graphic images, because they work by default with screen readers, copy/paste, and find-in-page. Also, they gracefully show fallback text if the font somehow fails to load, and they reflow if the browser window resizes. Not only that, color fonts are more flexible than graphic images, because they can incorporate the foreground color of the element using them into the design of the font.

    Color fonts are not meant to take the place of single-color fonts. But used in moderation, at a big enough size in the right circumstance, they can be the perfect icing on the cake!

    You can contact Myles C. Maxfield at or @Litherum, and you can contact David Jonathan Ross at or @djrrb, and you can find David’s work at

    May 25, 2022 02:00 PM

    May 16, 2022

    New WebKit Features in Safari 15.5

    Surfin’ Safari

    After the feature-packed release of Safari 15.4 two months ago, WebKit’s work for this version of Safari focused predominately on polishing existing features and fixing bugs.

    Safari 15.5 does contain three new technologies for web developers — support for the inert property in HTML; support for the worker-src Content Security Policy directive; and the new minimumViewportInset and maximumViewportInset APIs for implementing new CSS Viewport Units in WKWebView-based apps.

    Safari 15.5 is available for macOS Monterey 12.4, macOS Big Sur, macOS Catalina, iPadOS 15.5, and iOS 15.5. You can update to Safari 15.5 on macOS Big Sur and macOS Catalina by going to System Preferences → Software Update → More info, and choosing to update Safari.

    Developer Features

    Let’s look first at the HTML inert attribute. When set on an element, the attribute makes that element non-interactive by preventing mouse and keyboard focus, clicks, edits or text selection. It also hides the element from assistive technologies. For more information about inert, including a demo showing how inert can make the partially-offscreen content in a carousel visible, but inactive, read Non-interactive Elements with the inert Attribute.

    Next, let’s look at support for worker-src from Content Security Policy Level 3. The worker-src directive provides web developers a way to restrict which URLs are allowed to be sources for worker scripts (Worker, SharedWorker, or ServiceWorker). This can be used to prevent already loaded scripts from loading more scripts in the form of workers, a situation that has potential to be susceptible to malicious attack through using excessive CPU for computation. We also updated Content Security Policy console logging in Web Inspector.

    And last, we’ve added the minimumViewportInset and maximumViewportInset APIs to WKWebView so app developers can add support for all of the new CSS Viewport Units to their browser or other application on iOS, iPadOS and macOS. The minimumViewportInset corresponds to the large measurement, and maximumViewportInset corresponds to the small measurement. The new CSS Viewport Units, which shipped in Safari 15.4, include small (svw, svh, svi, svb, svmin, svmax), large (lvw, lvh, lvi, lvb, lvmin, lvmax), dynamic (dvw, dvh, dvi, dvb, dvmin, dvmax), and logical (vi, vb) units.

    Fixes and Polish

    Now, let’s get to the list of bug fixes and feature polish.


    • Fixed SVG tags behind modal dialogs to not be clickable
    • Fixed the Dialog element only animating once
    • Fixed rendering a USDZ loaded as the main resource
    • Fixed uploading “.pages” files to file inputs accepting “.pages” and “.jpeg” files

    Web API

    • Prevented BroadcastChannel from communicating across distinct opaque origins
    • Fixed respecting website policies during COOP-based process swap
    • Fixed PointerEvent.movementX always 0
    • Fixed resolving a fetch promise when a page enters page cache
    • Fixed pointer events to perform a hit test only if there is not a pointer capture target override
    • Fixed computing the site for cookies when the document is created by
    • Fixed Element.focus({preventScroll: true}) to correctly prevent scrolling on iOS


    • Fixed scrolling background-attachement: fixed
    • Fixed background-clip: text to work with display: flex
    • Fixed rendering for many position: sticky elements
    • Fixed position: sticky elements moving incorrectly while scrolling
    • Fixed text contents in <span> with opacity not updating when a sibling element has will-change: transform
    • Fixed :focus-visible matching on the wrong element when focused via script
    • Fixed text-shadow getting clipped
    • Fixed behavior of a position: sticky element within contain: paint
    • Fixed aspect-ratio with percentage widths
    • Fixed returning the default computed style for elements without the transition or animation shorthands


    • Aligned WebAuthn implementation to match specifications to use the default pubKeyCredParams list if the list in makeCredential is empty

    Content Security Policy

    • Fixed blocking image content in object elements
    • Fixed sending violation reports to the document for a detached element
    • Improved nonce hiding from the DOM
    • Updated Content Security Policy handling of JavaScript URLs


    • Fixed key rotation for single key audio in modern EME paired with a native HLS player
    • Fixed disabled Control Center spatial control when playing a video in Safari
    • Fixed loading a model in QuickLook when passing extra parameters
    • Fixed muted video that sometimes becomes paused when taken to fullscreen
    • Fixed video playback on iPhone 7
    • Fixed video playback for HEVC content encodings that generate many b-frames with a wide sliding window
    • Fixed HLS stream currentTime sometimes jumping backwards
    • Fixed clicking on the progress bar often pausing a YouTube video
    • Fixed blob videos slowing to pause
    • Fixed audio echo after the camera us paused or unpaused
    • Fixed playback of HTML5 embedded audio with unbounded range requests
    • Fixed the video poster disappearing prematurely on play, leaving a transparent video element


    • Fixed incorrect label returned by getUserMedia regardless of language selected
    • Reduced perceived audio latency


    • Fixed text wrapping for windows that exceed a certain width
    • Fixed a Korean webfont rendering issue
    • Fixed an issue where a transform change sometimes resulted in bad rendering
    • Fixed a flash of missing text content with transform-related animations
    • Changed to use colgroup for table sizing when it comes after thead, tbody, tfoot, or tr elements
    • Fixed two bopomofo tone marks to move to the correct place in vertical text with a particular bopomofo font

    Apple Pay

    • Fixed the Apple Pay Sheet to return billingContact on iOS


    • Fixed WebGL rendering when using preserveDrawingBuffer on iOS
    • Fixed a number of issues related to multisampling that were breaking a lot of WebGL content
    • Fixed handling TypedArray with AllowShared to be accepted
    • Fixed WEBGL_multi_draw validation

    Web Inspector

    • Fixed large message handling from remote devices
    • Fixed repeated opening and closing


    • Fixed launching Microsoft Teams from Safari


    • Fixed a noticeable delay in playback when rotating a full screen YouTube video

    Safari Extensions

    • Fixed a crash clicking on Safari App Extension toolbar items
    • Fixed an issue where SFContentBlockerManager.getStateOfContentBlocker() could return an incorrect value on iOS
    • Added support for optional_host_permissions for Safari Web Extensions


    We love hearing from you. Send a tweet to @webkit, @jensimmons or @jonathandavis to share your thoughts on this release. If you run into any issues, we welcome your feedback on the Safari UI or your WebKit bug report about web technology. Filing issues really does make a difference.

    Download the latest Safari Technology Preview to stay at the forefront of the web platform and to use the latest Web Inspector features. You can also use the WebKit Feature Status page to watch for new information about the web features that interest you the most.

    And More

    For more information on what’s in WebKit for Safari 15.5, read the Safari 15.5 release notes.

    These features were first released in Safari Technology Preview: 140, 141, 142, 143, 144, and 145.

    May 16, 2022 05:30 PM

    May 11, 2022

    Release Notes for Safari Technology Preview 145

    Surfin’ Safari

    Safari Technology Preview Release 145 is now available for download for macOS Big Sur and of macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 291957-293023. This release of Safari Technology Preview does not support versions of macOS Monterey prior to 12.3. Please update to macOS Monterey 12.3 or later to continue using Safari Technology Preview.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Sources tab
      • Allowed Response Local Overrides to map to a file on disk (r292084, r292120)

    :has() pseudo-class

    • Added invalidation support for the pseudo-classes :autofill (r292531); :placeholder-shown (r292523); :indeterminate, :read-only, :read-write, :required and :optional (r292466, r292582)

    Container Queries and Containment

    • Added CSSOM support (r292045)
    • Added support for contain: inline-size (r292394, r292416, r292465)
    • Added support for containment to disable the special handling of the HTML body element for overflow viewport propagation (r292127, r292157)
    • Corrected container selection for pseudo-elements (r292819)
    • Corrected container selection for ::slotted and ::part rules (r292635)
    • Disallowed invalid query range syntax (r292816)
    • Updated container shorthand order (r292759)

    CSS Grid

    • Added support for transitions and animations on grid-template-columns and grid-template-rows (r292432)
    • Fixed grid items that establish an independent formatting context to not be subgrids (r292524)
    • Implemented support for aligning baselines through subgrids (r292973)


    • calc() functions
      • Added NaN propagation for min, max, clamp, and hypot (r292732)
      • Serialized top level min, max, hypot as calc() (r292893)
    • resize property
      • Added support for block/inline CSS values (r292222)
      • Corrected minimum size computation to allow resizing below initial size (r292559)
    • Added support for rendering url(), CSS basic shapes other than path(), and coord-box for offset-path (r292382)
    • Fixed scrollIntoView with scroll-snap-type on root element (r292812)
    • Fixed drop-shadow filter to work correctly in tiled backing layer (r292059)
    • Fixed issue with position: sticky within contain: paint (r292155)
    • Implemented units for CSS Typed OM (r292150)

    Dialog Element

    • Dialog element now adapts to dark mode by default (r292029)


    • Allowed Wasm import from a JS Worker module behind the feature flag (r292799)
    • Changed ShadowRealm global object to have a mutable prototype (r292895)


    • Fixed full screen video progress bar flickering after dragging it (r292572)
    • Fixed MSE video not drawing onto canvas (r292811)
    • Fixed muted video that sometimes becomes paused when entering fullscreen (r292049)


    • Added support for all CTAP transports and remove gesture requirement for virtual authenticators (r292593)
    • Implemented getTransports() and getAuthenticatorData() (r292913)

    Web API

    • Removed the 1ms minimum for setTimeout (r291998)

    Content Security Policy

    • Improved compatibility of source matching (r292266)
    • Fixed WASM failing to execute after (r292229)


    • Fixed incorrect CORP and COEP check in 304 responses (r292595)

    Service Workers

    • Added support for ServiceWorkerClients.openWindow (r291979)
    • Implemented ServiceWorkerWindowClient.navigate (r292459)
    • Exposed workers as service worker clients and implemented registration matching for dedicated workers (r292861)
    • Fixed ensuring the document gets controlled by its matching service worker registration during a COOP-based process swap (r292468)
    • Fixed Service-Worker-Navigation-Preload header not being sent when Navigation Preload is enabled (r292296)
    • Fixed ServiceWorker.postMessage() not working from inside iframes (r292905)


    • Reduced perceived audio latency on streaming via WebRTC (r292563)

    May 11, 2022 09:24 AM

    May 02, 2022

    Release Notes for Safari Technology Preview 144

    Surfin’ Safari

    Safari Technology Preview Release 144 is now available for download for macOS Big Sur and of macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 291506-291957. This release of Safari Technology Preview does not support versions of macOS Monterey prior to 12.3. Please update to macOS Monterey 12.3 or later to continue using Safari Technology Preview.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Fixed page reloading and showing an empty inspector on pages with container queries (r291824)
    • Elements Tab
      • Fixed $0 not being displayed for the selected node after switching to another tab (r291729)
      • Fixed unwanted extra dash when autocompleting CSS variable names in the Styles panel (r291740)
      • Fixed inline swatch popovers not being hidden when the inline swatch is removed (r291628)
    • Console Tab
      • Fixed console.screenshot to no longer have extra transparent pixels at the bottom of viewport screenshots (r291519)


    • Added Typed OM support for container units (r291524)
    • Fixed CSS cascade regarding logical properties (r291546)
    • Fixed incorrect handling of NaN inside calc() for top-level calculation (r291911)
    • Let revert-layer roll back to presentational hints (r291594)
    • Implemented border-image serialization (r291537)
    • Preserved repeat() notation when serializing grid-templates (r291956)
    • Reduce memory usage for large, sparse grids (r291952)
    • Handled finite value with infinite step in round() for calc() (r291841)
    • Fixed incorrect resolution of percentage grid-gaps within subgrids (r291953)

    Web Animations

    • Enabled support for mutable timelines by default (r291868)


    • Changed Date.parse to stop returning numbers with fractional part (r291603)
    • Fixed class field initializer with extra parentheses (r291577)


    • Added getAssertion support for virtual HID authenticators (r291624)
    • Specified correct ASCPublicKeyCredentialKind in configureAssertionOptions (r291761)
    • Updated to pass along timeout to ASA and ignore timeout for conditional mediation requests (r291625)

    Web API

    • Added support for focused and visible ServiceWorkerWindowClient states (r291888)
    • Added a check for whether the origin can access storage in the Storage API (r291726)
    • Disabled custom storage paths for IndexedDB and LocalStorage by default (r291909)
    • Fixed PointerEvent.movementX to not always be 0 (r291886)
    • Fixed Context2D drawImage(img, x, y, w, h) to not throw IndexSizeError when width or height are 0 (r291748)
    • Fixed fetching a Blob URL with an unbounded Range header to correctly generate a Content-Range response header (r291622)
    • Implemented CSSNumericValue.mul, CSSNumericValue.div, CSSNumericValue.add, CSSNumericValue.sub, CSSNumericValue.max, and CSSNumericValue.min (r291597)
    • Implemented ServiceWorkerWindowClient.focus (r291938)


    • Included initial accessibility support for display: contents (r291570)


    • Fixed a bug where clicking anywhere on the progress bar pauses some MSE video implementations (r291629)
    • Fixed video playback for HEVC-encoded video with a lot of b-frames and a wide sliding window (r291813)

    Security Policy

    • Fixed website policies not being respected when doing COOP-based process swap (r291606)

    Web Extensions

    • Fixed a crash clicking on Safari App Extension toolbar items

    May 02, 2022 08:19 PM

    Víctor Jáquez: From gst-build to local-projects

    Igalia WebKit

    Two years ago I wrote a blog post about using gst-build inside of WebKit SDK flatpak. Well, all that has changed. That’s the true upstream spirit.

    There were two main reason for the change:

    1. Since the switch to GStreamer mono repository, gst-build has been deprecated. The mechanism in WebKit were added, basically, to allow GStreamer upstream, so keeping gst-build directory just polluted the conceptual framework.
    2. By using gst-build one could override almost any other package in WebKit SDK. For example, for developing gamepad handling in WPE I added libmanette as a GStreamer subproject, to link a modified version of the library rather than the one in flatpak. But that approach added an unneeded conceptual depth in tree.

    In order to simplify these operations, by taking advantage of Meson’s subproject support directly, gst-build handling were removed and new mechanism was set in place: Local Dependencies. With local dependencies, you can add or override almost any dependency, while flatting the tree layout, by placing at the same level GStreamer and any other library. Of course, in order add dependencies, they must be built with meson.

    For example, to override libsoup and GStreamer, just clone both repositories below of Tools/flatpak/local-projects/subprojects, and declare them in WEBKIT_LOCAL_DEPS environment variable:

    $ export WEBKIT_SDK_LOCAL_DEPS=libsoup,gstreamer-full
    $ export WEBKIT_SDK_LOCAL_DEPS_OPTIONS="-Dgstreamer-full:introspection=disabled -Dgst-plugins-good:soup=disabled"
    $ build-webkit --wpe

    By vjaquez at May 02, 2022 11:11 AM

    April 26, 2022

    Non-interactive Elements with the inert attribute

    Surfin’ Safari

    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.

    April 26, 2022 05:21 PM

    April 21, 2022

    WPE WebKit Blog: Happy birthday WPE!

    Igalia WebKit

    Welcome to the new Blog section on!

    Today is a special day for Igalia, especially for those colleagues that work on WebKit: Five years ago, on the 21st of April 2017, the WPE port was announced by our colleague Žan Doberšek on the WebKit mailing list.

    Let’s take some time to celebrate and recap how WPE evolved from the early prototyping days to the product empowering hundreds of millions of devices worldwide today.

    Celebrating WPEs 5th birthday with a cake

    WPE is … what exactly?

    To get everyone on the same page, let’s start by reiterating what WPE is: a WebKit port optimized for embedded devices. It allows you to embed a full-fledged Web browser engine that supports a large set of modern Web technologies into your product. WPE itself is not a Web browser such as Safari, Chrome or Firefox but contains the underlying building blocks to load, parse and render websites. To learn more about the distinction between a Web browser and a Web browser engine read our explainer.

    You might ask yourself, what does “optimized for embedded devices” mean in practice? Unlike most other WebKit ports, WPE does not rely on a specific user-interface toolkit, such as Qt, GTK, Cocoa, etc., nor does it offer any integration with these kinds of toolkits. WPE WebKit is light-weight, integrates well with a variety of hardware configurations, and only requires a minimum set of APIs on your side: EGL and OpenGL ES 2.

    The early days 2014 - 2017

    The idea for a new WebKit port was born in 2014, as part of a collaboration between Metrological and Igalia. The goal of this collaboration was to have a WebKit port running efficiently on their set-top boxes, utilizing a modern Wayland based Linux graphics architecture. Back then, QtWebKit was popular among embedders; however, it was unmaintained and its future was unclear since Qt wanted to transition from using WebKit to Blink.

    In September 2014 a group of Igalians forked the WebKitGtk port, removed all GTK toolkit dependencies, and prototyped what was necessary to achieve the goal: rendering websites without involving any of the traditional toolkits and instead utilizing a Wayland-based rendering approach.

    During development it became apparent that this WebKit port is generally useful for all our customers and the community as a whole. Therefore Igalia decided to aim for an even more flexible design, where Wayland is only one of the possible backends. Our fellow Igalian Miguel Gomez reported in his late 2016 blog post about this change, and the renaming of the port: WPE appears for the first time in public.

    The project’s removal of the Wayland dependency and the subsequent reorganization lead to the architecture we have today, consisting of not only the WPE port itself but a whole ecosystem of projects such as libwpe, WPEBackend-fdo, WPEBackend-rdk, etc., that together form the WPE project.

    2017 - today

    After months of focused engineering efforts, the downstream work was finished and Igalia was ready to announce WPE to the public on the 17th of April 2017, with the promise that Igalia will maintain the port alongside the existing WebKitGtk port. That is not a cheap bill: maintaining an upstream port is a recurring multi-million dollar investment. Just in order to keep the port itself healthy, as updates are made all around it, requires infrastructure, bots and a team of fully dedicated engineers to deal with maintenance, testing, triaging, tickets, etc. To implement new Web standards, fix related bugs or design and contribute features requires an even more considerable amount of resources.

    Since then, Igalia ramped up the WPE investments and steadily advanced the port while helping customers to integrate WPE into their environments. Today WPE is healthy, runs on many platforms, and offers the most flexible browser architecture at present. Also, thanks in great part to this work, Igalia was responsible for nearly 16.5% of all commits in WebKit itself last year, helping make the larger project and ecosystem around it healthier too.

    However, none of this would be possible without the commitment of many Igalians pushing the project forward every day for the past 8 years. A new People Behind WPE series will be launched soon: over the following months, the Igalians involved with WPE will introduce themselves, their area of expertise, and talk about a specific WPE related technical topic. You’ll get to know the people behind the product and a first-class technical overview of individual parts of the WPE architecture! We plan to release a new article every 3-4 weeks, so be sure to visit again soon and enjoy the upcoming People Behind WPE series.

    Feel free to spread the word and make noise about WPE. Stay healthy, stay tuned!

    April 21, 2022 12:00 AM

    April 19, 2022

    Manuel Rego: Web Engines Hackfest 2022

    Igalia WebKit

    Once again Igalia is organizing the Web Engines Hackfest. This year the event is going to be hybrid. Though most things will happen on-site, online participation in some part of the event is going to be possible too.

    Regarding dates, the hackfest will take place on June 13 & 14 in A Coruña. If you’re interested in participating, you can find more the information and the registration form at the event website:

    What’s the Web Engines Hackfest?

    This event started a long way back. The first edition happened in 2009 when 12 folks visited the Igalia offices in A Coruña and spent there a whole week working on WebKitGTK port. At that time, it was kind of early stages on the project and lots of work was needed, so those joint weeks were very productive to move things forward, discuss plans and implement features.

    As the event grew and more people got interested, in 2014 it was renamed to Web Engines Hackfest and started to welcome people working on different web engines. This brought the opportunity for engineers of the different browsers to come together for a few days and discuss different features.

    The hackfest has continued to grow and these days we welcome anyone that is somehow involved on the web platform. In this year’s event there will be people from different parts of the web platform community, from implementors and spec editors, to people interested in some particular feature.

    This event has an unconference format. People attending are the ones defining the topics, and work together in breakout sessions to discuss them. They could be issues on a particular browser, generic purpose features, new ideas, even sometimes tooling demos. In addition, we always arrange a few talks as part of the hackfest. But the most important part of the event is being together with very different folks and having the chance to discuss a variety of topics with them. There are not lots of places where people from different companies and browsers join together to discuss topics. The idea of the hackfest is to provide a venue for that to happen.

    2022 edition

    This year we’re hosting the event in a new place, as Igalia’s office is no longer big enough to host all the people that will be attending the event. The venue is called Palexco and it’s close to the city center and just by the seaside (with views of the port). It’s a great place with lots of spaces and big rooms, so we’ll be very comfortable there. Note that we’ll have childcare service for the ones that might need it.

    New venue: Palexco (picture by Jose Luis Cernadas Iglesias) New venue: Palexco (picture by Jose Luis Cernadas Iglesias)

    The event is going to be 2 days this time, 13th and 14 June. Hopefully the weather will be great at that time of the year, and the folks visiting A Coruña should be able to really enjoy the trip. There are going to be lots of light hours too, sunrise is going to be around 7am and sunset past 10pm.

    The registration form is still open. So far we’ve got a good amount of people registered from different companies like: Arm, Deno Land, Fission, Google, Igalia, KaiOS, Mozilla, Protocol Labs, Red Hat and Salesforce.

    Arm, Google and Igalia will be sponsoring 2022 edition, and we’re really thankful for your support! If your company is also interested in sponsoring the hackfest, please contact us at

    Apart from that there are going to be some talks that will be live streamed during the event. We have a Call For Papers with a deadline by the end of this month. Talks can be on-site or remote, so if you’re interested on giving one, please fill the form.

    We know we’re in complex times and not everyone can attend onsite this year. We’re sorry about that, and we hope you all can make it in future editions.

    Looking forward to the Web Engines Hackfest 2022!

    April 19, 2022 10:00 PM

    April 11, 2022

    Private Click Measurement: Conversion Fraud Prevention and Replacement For Tracking Pixels

    Surfin’ Safari

    Welcome to the fourth feature update on Private Click Measurement, our proposed web standard for measuring advertising in a privacy-preserving way. More precisely three major and two minor updates to PCM, all available in iOS/iPadOS 15.4 and macOS Monterey 12.3.

    Major updates:

    • Conversion fraud prevention. This enables merchant websites to sign unlinkable tokens and get proof in the attribution report that it was triggered by a trustworthy conversion event.
    • Replacement for third-party tracking pixels. Merchant websites can now trigger a conversion event through a same-site pixel which removes the need to call any third-parties in PCM.
    • Measurement of clicks in cross-site iframes. Many publishers want to isolate ads on their site in cross-site iframes. Now clicks in such iframes can be measured too.

    What is Private Click Measurement?

    Private Click Measurement, or PCM, is a proposed web standard for measuring the effectiveness of click-through advertising in a privacy-preserving way. It allows for 8 bits of data on the click source site to be combined with 4 bits of data on the click destination site to measure which clicks are driving conversions. The combined 8+4 bits of data is sent to both the click source and destination in an attribution report that doesn’t carry any user or device identifiers. The net result is a report that says “Someone who clicked ad X on website A later converted with value Y on website B.”

    PCM was made available as a beta in May 2019, and then shipped 2021 in iOS/iPadOS 14.5 and in Safari 14.1 on macOS. Its privacy-preserving nature means it can be used without getting the user’s permission to track according to AppTrackingTransparency.

    Conversion Fraud Prevention

    In July 2021 we presented our beta feature for click fraud prevention in PCM. It allows the click source website to sign an unlinkable token at the time of the click navigation, which results in a signed token being included in the resulting attribution report. Today we present the same capability for the click destination website.

    PCM’s triggering event is a redirect to the well-known path .well-known/private-click-measurement/trigger-attribution/. Now that redirect supports a query string parameter called attributionDestinationNonce. If a nonce is included in the redirect, it triggers WebKit to ask the click destination server to sign an unlinkable token.

    Step 1: Generate an RSA Key Pair

    Unlinkable tokens require a public key for token generation and validation, and a corresponding private key for signing. PCM supports three different RSA key sizes: 2048, 3072 and 4096 bits. The expected encoding of the public key is a Base64URL encoded (using RFC4648 section 5) SPKI with the RSA-PSS OID and the parameters corresponding to the RSABSSA IETF draft. Examples of such key encodings are included in an earlier blog post.

    Step 2: Add the Query Parameter attributionDestinationNonce

    The triggering event should look like this to opt in to conversion fraud prevention:

    … redirects to …

    The attributionDestinationNonce is only in place to help the click destination server know the context for which it’s signing an unlinkable token. When the click destination server is asked to sign an unlinkable token, it’ll get the attributionDestinationNonce and can make a decision as to whether the triggering event was trustworthy or not.

    The attributionDestinationNonce needs to be a Base64URL encoded 128-bit/16-byte value. Any smaller or larger value will cancel the issuance flow of the fraud-prevention signature. Any non Base64URL encoded value will also cancel the token transaction. Web Inspector will log a warning if the attributionDestinationNonce is malformed.

    Step 3: Respond to a Request for Your Public Key

    The browser and any validating party needs to be able to fetch your public key at any point in time from this well-known location: https://clicksource.example/.well-known/private-click-measurement/get-token-public-key/.

    Replacement For Third-Party Tracking Pixels

    Previous versions of PCM required HTTP requests on the click destination site to go to the click source site. This was designed to enable reuse of existing cross-site tracking pixels that no longer carry cookies under tracking prevention.

    For new adoption of PCM, for instance onboarding a new publisher site where ads are shown, there’s no need to add such legacy pixels. Longer term, we want to remove support for triggering events through cross-site tracking pixels since even though they don’t carry cookies, they do ping third-party domains that may be categorized as trackers. Many websites want to be completely tracker-free and we’re happy to be able to support that with PCM while still allowing for click measurement across websites.

    Click destination sites can now signal a triggering event through a same-site pixel. It looks like below.

    Same-site subresource request to:

    … redirects to same-site well-known location:

    The above redirect tells PCM to schedule an attribution report if there is a pending attribution from clickSource.example.

    Why not a JavaScript API to trigger attribution without cross-site pixels, you may ask? There are two reasons for that:

    • The standards conversation in W3C Privacy CG told us that some click destination sites have a policy against placing third-party scripts on their websites because of the risky dependencies they introduce. We think that’s a very valid concern, not in the least for user privacy. However, those same click destination sites do not have the same strict policies against pixels. We can given them the API functionality they need by introducing same-site pixels. They can be triggered through actual image elements or through a JavaScript Fetch.
    • We continue to consider it very important that websites should not be able to tell at page load time whether or not the user has PCM (or other ad measurement) features enabled. This is to protect the user’s right to a choice. Pixel APIs don’t reveal if the triggering event is accepted or not. A JavaScript API could be made to not reveal that info either but pixel APIs just do that.

    Measurement of Clicks in Cross-Site Iframes

    One of the first PCM change requests from the web community was to allow for measurement of clicks that happen in cross-site iframes. This would allow for measurement of advertising isolated in iframes. We’re happy to announce that we now support that. Note that the resulting attribution report is still sent to the two first-party websites, often referred to as the publisher and the merchant.

    WebIDL Attributes Now In Camelcase

    JavaScript access to PCM’s anchor tag attributes now requires camel-casing. This is the result of interoperability work in web standards. Here’s how it looks:

    <a id="test" href="https://clickDestination.example" attributionsourceid=40 attributiondestination="https://clickDestination.example"></a>
        const anchorTag = document.getElementById("test");
        anchorTag.attributionSourceId …;
        anchorTag.attributionDestination …;

    Encoding Update for Token Keys

    When sending your public key to the browser, it needs to be a 2048, 3072 or 4096 bit RSA public key wrapped with an RSAPSS OID in ASN1 format. See “Step 1: Generate an RSA Key Pair above.” Those bytes then need to be base64 encoded with the URL and filename safe alphabet. Examples of such key encodings are included in an earlier blog post.

    April 11, 2022 04:18 PM

    April 07, 2022

    Release Notes for Safari Technology Preview 143

    Surfin’ Safari

    Safari Technology Preview Release 143 is now available for download for macOS Big Sur and of macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 290223-291506. This is the last release of Safari Technology Preview that will support versions of macOS Monterey prior to 12.3. Please update to macOS Monterey 12.3 or later to continue using Safari Technology Preview.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Elements Tab
      • Added option in the Layout panel of the Details Sidebar for Flexbox overlays to show each item’s CSS order and/or DOM index in the parent flex container (r290613)
    • Service workers are no longer terminated while they are being inspected. (r291467)

    CSS Container Queries

    • Added support for nested container queries (r290257)
    • Added basic support for container units (r291474)
    • Changed to only apply inline-size containment when it is allowed (r291154)
    • Ensured container style changes are propagated to descendants (r291500)
    • Fixed getComputedStyle to update the style for invalid query containers (r290320)
    • Fixed offsetWidth, offsetHeight, and similar to update layout for container queries (r290380)
    • Implemented new container selection algorithm (r291098)

    CSS Cascade Layers

    • Added support for revert in @keyframes (r290457)
    • Added support for revert-layer in @keyframes (r290729)
    • Fixed revert on low-priority properties (r291260)
    • Let revert-layer in the lowest layer roll back to user styles (r290864)


    • Fixed computing the correct containing block override size for items that are subgridded in one dimension only (r290577)
    • Converted grid direction to be relative to subgrid when accounting for extra margin (r290576)
    • Handled reverse flow direction when converting iterator coords for a subgrid (r290572)
    • Fixed subgrid items to always be stretched (r291464)
    • Fixed positioning of position: absolute items within nested grids (r290674)


    • Implemented text-decoration as a shorthand (r290756, r291244)
    • Implemented logical properties for CSS overscroll-behavior (r290422)
    • Fixed background-clip: text to work with display: flex (r291303)


    • Added CalendarDateTime parsing (r290248)
    • Fixed ShadowRealm unwinding (r290283)
    • Fixed Temporal.PlainDate to validate input range (r290282)


    • Fixed image alt text bi-directional reordering (r290734)
    • Fixed alt text position in right-to-left context (r290726)
    • Fixed table sizing when colgroup comes after tbody (r290512)
    • Fixed scrollbars disappearing when very long or wide (r290545)
    • Handled perpendicular containing blocks when computing available logical height (r290634)

    Web Animations

    • Added support for passing an optional timeline to Element.animate() (r290655)
    • Changed setting the composite property on a keyframe effect to invalidate the target style (r290741)
    • Fixed inherit values should trigger keyframe recomputation if any previous effect has changed that property (r290831)
    • Fixed keyframe values set to inherit to recompute their values when the inherited value changes (r290823)
    • Fixed animating fill and stroke to or from currentColor (r290898)
    • Fixed ::placeholder to not be a valid pseudo-element for a keyframe effect target (r290662)
    • Fixed animations on modal <dialog> elements and ::backdrop to run more than once (r291282)
    • Fixed outline-width with transition to animate correctly (r290735)
    • Fixed text-emphasis shorthand to be animatable (r290895)
    • Fixed text-emphasis-color to support non-discrete animations (r290887)
    • Made changes to font-size recompute keyframes (r290730)
    • Added support for discrete animation to:


    • Fixed clamping animated values to the 0-1 range for:


    • Allowed history swipe in scroller with overscroll-behavior (r291497)
    • Fixed incorrect painting when scrolling a page with fixed backgrounds (r290785)
    • Fixed scroll animation when scroll snap scroller is navigated with the keyboard (r290548, r290625)
    • Fixed element with position: sticky after sticking, starting to move incorrectly when scrolling (r290812)


    • Added support for authenticatorSelection.residentKey (r291176)
    • Added fallback to attestation=none if requested but unavailable for platform authenticator (r290539)
    • Enabled using WebAuthn within cross-origin iframe elements (r291018)
    • Improved virtual authenticator support ( r291423 , r291321)


    • Fixed WebGL rendering incorrect results when using preserveDrawingBuffer (r291218)


    • Enabled the inert attribute by default (r290587)

    Web API

    • Enabled the Permissions API by default (r291116, r290301)
    • Fixed mousemove events double-firing in Safari (r290743)
    • Fixed rendering when loading a USDZ as the main resource (r290562)
    • Fixed CORS preflight failing due to cache-control header (r290507)
    • Fixed PerformanceNavigationTiming Response Start being unavailable when using Service Worker Cache (r291441)
    • Fixed buffered flag not working in Paint Timing (r290247)
    • Fixed load event never firing after a form is submitted (r290841)
    • Fixed WebSocket.send() to synchronously update bufferedAmount (r290995)
    • Made input element UA shadow tree creation lazy (r290284)
    • Made pointer-events checks for SVG take in account inert subtrees (r290306)
    • Removed the 1ms minimum for setTimeout (r291476)


    • AudioContext will continue playing when minimizing or moving the macOS Safari window to the background (r291267)
    • Fixed scrambled output for some WebM videos with VP8 codec (r291216)
    • Implemented remote-inbound-rtp packetsLost (r290865)


    • Updated WebSpeech API support (r291124)

    File System Access

    • Changed to throw an exception if a file or directory cannot be accessed in file system (r291014)
    • Disallowed empty name in FileSystemHandle (r290998)
    • Disallowed names that are not permitted by the underlying file system (r291057)
    • Fixed fetching website data that may get a wrong or missing record after migrating data to general storage directory (r290239, r290233)

    Web Extensions

    • Fixed cookie expiration dates so they are not off by 30 years

    April 07, 2022 10:19 PM

    Manuel Rego: :focus-visible is shipping in Safari/WebKit

    Igalia WebKit

    This is the final report about the work Igalia has been doing to add support for :focus-visible in WebKit. As you probably already know this work is part of the Open Prioritization campaign by Igalia that has been funded by different people and organizations. Big thanks to all of you! If you’re curious and want to know all the details you can find the previous reports on this blog.

    The main highlight for this blog post is that :focus-visible has been enabled by default in WebKit (r286783). 🚀 This change was included in Safari Technology Preview 138, with its own post on the official WebKit blog. And finally reached a stable release in Safari 15.4. It’s also included in WebKitGTK 2.36 and WPE WebKit 2.36.

    Open Prioritization

    Let’s start from the beginning, my colleague Brian Kardell had an idea to find more diverse ways to sponsor the development of the web platform, after some internal discussion that idea materialized into what we call Open Prioritization. In summer 2020 Igalia announced Open Prioritization that intially had six different features on the list:

    • CSS lab() colors in Firefox
    • :focus-visible in WebKit/Safari
    • HTML inert in WebKit/Safari
    • Selector list arguments for :not() in Chrome
    • CSS Containment support in WebKit/Safari
    • CSS d (SVG path) support in Firefox

    By that time I wrote a blog post about this effort and CSS Containment in WebKit proposal and my colleagues did the same for the rest of the contenders:

    After some months :focus-visible was the winner. By the end of 2020 we launched the Open Prioritization Collective to collect funds and we started our work on the implementation side.

    Last year at TPAC, Eric Meyer gave an awesome talk called Adventures in Collective Implementation, explaining the Open Prioritization effort and the ideas behind it. This presentation also explains why there’s room for external investments (like this one) in the web platform, and that all open source projects (in particular the web browser engines) always have to make decisions regarding priorities. Investing on them will help to influence those priorities and speed up the development of features you’re interested in.

    It’s been quite a while since we started all this, but now :focus-visible is supported in WebKit/Safari, so we can consider that the first Open Prioritization experiment has been successful. When :focus-visible was first enabled by default in Safari Technology Preview early this year, there were lots of misunderstandings about how the development of this feature was funded. Happily Eric wrote a great blog post on the matter, explaining all the details and going over some of the ideas from his TPAC talk.

    :focus-visble is shipping in in WebKit, how that happened?

    In November last year, I gave a talk at CSS Conf Armenia about the status of things regarding :focus-visible implementation in WebKit. In that presentation I explained some of the open issues and why :focus-visible was not enabled by default yet in WebKit.

    The main issue was that Apple was not convinced about not showing a focus indicator (focus ring) when clicking on a focusable element (like a <div tabindex="0">). However this is one of the main goals of :focus-visible itself, avoiding to get a focus indicator in such situations. As Chromium and Firefox were already doing it, and aiming to have a better interoperability between the different implementations, Apple finally accepted this behavioral change on WebKit.

    Then Antti Koivisto reviewed the implementation, suggesting a few changes and spotting some issues (thanks about that). Those things were fixed and the feature was enabled by default in the codebase last December. As usual once a feature is enabled some more issues appear and they were fixed too. Including even a generic issue regarding accesskey on focusable elements, which required to add support to test accesskey on WebKit Web Platform Tests (WPT).

    As part of all this work since my previous blog post we landed 9 more patches on WebKit, making a total of 36 patches for the whole feature, together with a few new WPT tests.

    Buttons and :focus-visible on Safari

    This topic has been mentioned in my previous posts and also in my talk. Buttons (and other form controls) are not mouse focusable in Safari (both in macOS and iOS), this means that when you click a button on Safari, the button is not focused. This behavior has the goal to match Apple platform conventions, where the focus doesn’t move when you click a button. However Safari implementation differs from the platform one, as the focus gets actually lost when you click on such elements. There are some very old issues in WebKit bugtracker about the topic (see #22261 from 2008 or #112968 from 2013 for example).

    There’s a kind of coincidence related to this. Before :focus-visible existed, buttons were never showing a focus indicator in Safari after mouse click, as they are not mouse focusable. This was different in other browsers where a focus ring was showed when clicking on buttons. So while :focus-visible fixed this issue for other browsers, it didn’t change the default behavior for buttons in Safari.

    However with :focus-visible implementation we introduced a problem somehow related to this. Imagine a page that has an element and when you click it, the page moves the focus via script (using HTMLElement.focus()) to a different element. Should the new focused element show a focus indicator? Or in other words, should it match :focus-visible?

    ol > li::marker { content: counter(list-item) ") "; }

    The answer varies depending on whether the element clicked is or not mouse focusable:

    1. If you click on a focusable element and the focus gets moved via script to a different element, the newly focused element does NOT show a focus indicator and thus it does NOT match :focus-visible.
    2. If you click on a NON focusable element and the focus gets moved via script to a different element, the newly focused element shows a focus indicator and thus it matches :focus-visible.

    All implementations agree on this, and Chromium and Firefox have been shipping this behavior for more than a year without known issues so far. But a problem appeared on Safari, because unlike the rest of browsers, buttons are not mouse focusable there. So when you click a button in Safari, you go to point 2) above, and end up showing a focus indicator in the newly focused element. Web authors don’t want to show a focus indicator on that situations, and that’s something that :focus-visible is fixing through point 1) in the rest of browsers, but not in Safari (see bug #236782 for details).

    We landed a workaround to fix this problem in Safari, that somehow adds an exception for buttons to follow point 1) even if they are not mouse focusable. Anyway this doesn’t look like the solution for the long term, and looking into making buttons mouse focusable on Safari might be the way to go in the future. That will also help to solve other interop issues.

    And now what?

    The feature is complete and shipped, but as usual there are some other things that could be done as next steps:

    • The :focus-visible specification is kind of vague and has no normative text related to when or not show a focus indicator. This was done on purpose to advance on this area and have flexibility to adapt to user needs. Anyway now that all 3 major web engines agree on the implementation, maybe there could be the chance to define this in some spec. We tried to write a PR for HTML spec when we started the work on this feature, at that time it was closed, probably it was not the right time anyway. But maybe something like that could be retaken at some point in the future.
    • WebKit Web Inspector (Dev Tools) don’t allow you to force :focus-visible yet. We sent a patch for forcing :focus-within first but some UI refactoring is needed, once that’s done adding support for :focus-visible too should be straight forward.
    • Coming back to the topic on buttons not being mouse focusable in Safari. The web platform provides a way to make elements not keyboard focusable via tabindex="-1". Why not providing a way to mark an element as not mouse focusable? Maybe there could be a proposal for a new HTML attribute that allows making elements not mouse focusable, that way websites could mimic Apple platform conventions. There are nice use cases for this, for example when you’re editing an input and then you click on some button to show some contextual information, with something like this you could avoid losing the focus from the input to carry on with your editing.


    So yeah after more than a year since Igalia started working on :focus-visible in WebKit, we can now consider that this work has been complete. We can call the first Open Prioritization experiment a success, and we can celebrate together with all the people that have supported us during this achievement. 🎉

    Thank you very much to all the people that sponsored this work. And also to all the people that helped reviewing patches, reporting bugs, discussing things, etc. during all this time. Without all your support we won’t be able to have made this happen. 🙏

    Last but not least, we’d like to highlight how this work has helped the web platform as a whole. Now the major web browser engines have shipped :focus-visible and are using it in the default UA stylesheet. This makes tweaking the focus indicator on websites easier than ever.

    April 07, 2022 10:00 PM

    March 24, 2022

    Release Notes for Safari Technology Preview 142

    Surfin’ Safari

    Safari Technology Preview Release 142 is now available for download for macOS Big Sur and macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 289213-290223.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Elements Tab
    • Sources Tab
      • Fixed clicking to re-enable a breakpoint clearing automatic continue (r289669)
      • Fixed double-clicking a breakpoint icon to show the edit popover (r289670)

    CSS Subgrid

    • Enabled subgrid by default (r290111)
    • Added accounting for subgrid margin, border, and padding when sizing (r290096)
    • Added support for parsing subgrid in grid-template-columns and grid-template-row (r289722)
    • Changed to copy track sizes from the parent grid into subgrid (r290007)
    • Changed to inherit track count from the parent grid for subgridded axes and clamp item placement to that explicit grid (r289986)
    • Included subgrid items in the track sizing algorithm of the outer grid (r290077)
    • Implemented getComputedStyle for subgrids (r289993)
    • Fixed certain scenarios where grid may be empty (r289437)
    • Fixed recalculating styles when updating a grid gap value (r289241)

    CSS Container Queries

    • Enabled CSS Container Queries by default (r290025)
    • Added query container tracking so they can be invalidated on size change (r289457)
    • Added support for all size features (r289838)
    • Added support for full range notation in size queries (r290037)
    • Added support for range operators in size queries (r289789)
    • Changed size queries on unsupported axis to evaluate to unknown (r289890)
    • Implemented container name matching (r289617)
    • Implemented full query parser and evaluator (r289742)
    • Implemented inline-size containment (r289466)
    • Implemented container shorthand serialization (r289886)
    • Matched container queries correctly in non-rendered subtrees (r290205


    • Enabled CSS Motion Path by default (r290071)
    • Enabled overscroll-behavior by default (r289683)
    • Changed transform: perspective(0) to not be considered an identity operation (r289903)
    • Changed to return none for the computed style of a mask when there are no mask images (r289377)
    • Fixed :focus-visible with a click on radio or checkbox labels (r289521)
    • Fixed contain: content in fullscreen (r289686)
    • Fixed incorrect absolute position layout when toggling contain (r289527)
    • Updated conversion to a color space with a smaller gamut to perform gamut mapping (r289396)
    • Updated an element with both -webkit-user-select: all and -webkit-user-drag: element to get a snapshot as when using only -webkit-user-drag: element (r289544)

    Web Animations

    • Enabled the KeyframeEffect.composite property (r290067)
    • Added composite accumulation support for transform properties (r289599)
    • Added support for logical properties in JS-originated animations (r289216)
    • Aligned animations with different, but compatible, frameRate values (r290121)
    • Allowed setting frameRate as an option passed to Element.animate() (r290123)
    • Allowed setting frameRate as an option passed to document.timeline.animate() (r290125)
    • Changed to properly handle interpolation of non-invertible matrices (r289862)
    • Changed to use the animation frameRate during animation resolution and scheduling (r290003)
    • Changed Animation.commitStyles() to use the non-animated style (r289453)
    • Fixed additive and accumulation interpolation to work correctly with implicit 0% and 100% keyframes (r289454)
    • Fixed animating from scale() to scale() translate() (r289732)
    • Fixed additive animations to prevent other animations from running accelerated (r289605)
    • Fixed recomputing keyframes when changing direction or writing-mode (r289426)
    • Fixed clearing computed keyframes when changing direction or writing-mode (r289226)
    • Fixed animations associated with a custom effect to appear in document.getAnimations() result (r290122)
    • Implemented parsing and animation support for offset shorthand (r289876)


    • Fixed tab characters and ch units to obey synthetic bold width adjustments correctly (r289609)


    • Changed input elements to return an empty string for an invalid floating-point number that ends with “.” (r290124)
    • Fixed selection method return values to match the spec (r289813)
    • Fixed light appearance text fields rendering invisible in Increased Contrast mode (r290054)
    • Fixed clicking an <input type="image"> submitting the form with a null submitter (r289615)
    • Improved applyStep() to match the spec (r289465)


    • Enabled Shadow Realms support by default (r290119)
    • Implemented Temporal.PlainDate behind a flag: __XPC_JSC_useTemporal=1 (r290209)
    • Optimized JSString’s atomization (r289359)

    Shared Workers

    • Added support for sharing Shared Workers (including across WebProcesses) (r289247)
    • Changed to fail synchronously when constructing a SharedWorker with a URL that is not same-origin (r289532)
    • Fixed self.location.href in Shared Workers in case of redirects (r289483)


    • Changed worker scripts to always be decoded as UTF-8 (r289489)
    • Fixed WorkerGlobalScope.importScripts() to protect blob URLs that were passed in until the imports are done (r289236)
    • Fixed MIME type check for classic worker script fetches (r289672)
    • Fixed exceptions to be properly reported when initializing a worker as a module (r289479)

    Dialog Element

    • Fixed the Dialog element only animating once (r289498)
    • Exposed the correct role, subrole, and role description properties for the <dialog> element (r289713)

    Web API

    • Added support for both versions of ScreenCaptureKit API (r289547)
    • Changed to use the system window and screen picker when available (r289696, r289701)
    • Fixed settling a fetch promise to be delayed in case the page is entering page cache (r289533)
    • Optimized DOM storage event dispatch (r290223)

    Experimental Model Element

    • Improved sizing on macOS (r289495)
    • Made standalone model documents interactive (r289666)

    Web Extensions

    • Added support for the browser.action.openPopup() and browser.browserAction.openPopup() API to open the extension popup for a specific window (this is a WECG proposal)
    • Added support for the optional_host_permissions manifest key in manifest_version 3 extensions (this is a WECG proposal)
    • Added support for browser.runtime.getFrameId() so it easier to get frame identifiers from content scripts (this is a WECG proposal)
    • Added support for the frameId option that can be passed to browser.tabs.sendMessage()
    • Fixed the number returned by parentFrameId for webNavigation and webRequest events to be -1 when it is the main frame
    • Fixed devtools.panels.onShown so the window object of the panel is sent to the listeners
    • Fixed devtools.panels.onShown to prevent it from firing multiple times per active panel change
    • Made the devtools API namespace only exposed to the devtools background pages

    March 24, 2022 12:18 AM

    March 14, 2022

    New WebKit Features in Safari 15.4

    Surfin’ Safari

    With over 70 additions to WebKit, Safari 15.4 is packed with new web technologies, updates, and fixes. We’ve assembled a huge release as part of our commitment to web developers, and the people who use the web. This is the first big WebKit release of 2022, and we’re just getting started.

    Safari 15.4 is available for macOS Monterey 12.3, macOS Big Sur, macOS Catalina, iPadOS 15.4, and iOS 15.4. You can update to Safari 15.4 on macOS Big Sur and macOS Catalina by going to System Preferences → Software Update → More info, and choosing to update Safari.


    Let’s start with HTML. WebKit added support for lazy-loading images with the loading attribute on the <img> element, providing web developers with an easy way to instruct the browser to defer loading certain images until the user scrolls near them.

    After years of standardization debates over accessibility considerations and with a solution finally at hand, WebKit added support for the <dialog> element and ::backdrop pseudo-element. The <dialog> element provides a robust and powerful way to create overlays and modals.

    <dialog id="confirmation-dialog">
        <h1>Do you want to delete everything?</h1>
        <p>You will lose all your data.</p>
        <button id="cancel-delete">Cancel</button>
        <button id="confirm-delete">Delete!</button>

    The ::backdrop pseudo-element makes it possible to style the background underneath the modal.

    This is a pretty dialog

    Pretty dialog example styled

    You can learn all about using <dialog> and ::backdrop in Introducing the Dialog Element.

    WebKit also added support for the global autofocus attribute allowing developers to indicate which element should be the one in focus when the page loads or when a <dialog> is displayed.


    Features for CSS Architecture

    Several additions to CSS in 2022 offer revolutionary new ways for web developers to architect their code, making it easier to reuse code, create design systems, and integrate with complex applications.

    Landing in Safari first, WebKit added support for the :has() pseudo-class. This selector fulfills a long-expressed desire for a “parent selector” — a way to apply CSS rules conditionally based on the contents of an element — and goes even further with the possibilities it enables. It was long thought such a selector was not possible, but our team figured out a way to highly-optimize performance and deliver a flexible solution that does not slow the page.

    WebKit added support for Cascade Layers — 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 web developer could create a “framework” layer and a “custom” layer — assigning all the CSS from a 3rd-party framework to the “framework” layer, and 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 used in each layer. Cascade Layers is arriving in all major browsers at about the same time and is included in Interop 2022, ensuring this is a tool web developers can begin to seriously consider for the future.

    WebKit also added support for CSS Containment — all four types: size, layout, style, and paint — with the contain property.

    Solving Pain Points

    Several more additions to CSS in WebKit introduce solutions to long-standing pain points.

    Web developers often ask for a tool that would work similar to existing 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. And 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. To cover the small, large, and dynamic versions of vmin and vmax, the svmin, svmax, lvmin, lvmax, dvmin, and dvmax units were implemented. To support logical dimensions, the new vi and vb are similar to existing Viewport Units, in the viewport inline and viewport block dimensions. And svi, svb, lvi, lvb, dvi, and dvb provide logical dimension units for the small, large, and dynamic versions of the inline and block dimensions. WebKit is happy to lead the pack, shipping these new units first and encouraging other browsers to do so through Interop 2022.

    WebKit added support for the :focus-visible pseudo-class to style the focus indicator only when the browser renders it. Learn more by reading The Focus-Indicated Pseudo-class :focus-visible.

    To make native form controls more customizable, the accent-color property provides a way for web developers to alter the color of particular parts of the form control UI. Accent color is supported for <input type="checkbox">, <input type="radio">, <progress>, <select>, and text-input types with a <datalist> on macOS, iPadOS, and iOS. Additionally, on iPadOS and iOS, accent color is supported for <input type="range">, <button>, and <input type="button">.

    WebKit fixed a bug with the interpolation between colors with alpha transparency — improving gradient support.

    How a gradient looks before & after this fix. Without the fix, the color is muddy.

    WebKit added support for calc() math functions including sin, cos, tan, e, pi, exp, log, atan, acos, asin, and atan2.


    Several new WebKit features in Safari 15.4 enrich what’s possible in typography on the web.

    WebKit added support for the font-palette CSS property and @font-palette-values rule. The font-palette property provides a way for web developers to select one of several different pre-defined color palettes contained inside a color font — for example, to declare that a font’s dark color palette be used for the site’s dark mode design. The @font-palette-values rule provides a way for web developers to define their own custom color palette for recoloring color fonts.

    four versions of a drop cap, in orange, purple, yellow, and black.The color font used for the enlarged caps is Bradley Initials DJR Web, shown here with its default palette, a customized palette created by the web developer, an alternative palette that’s included with the font, and with color removed by the user’s preference.

    WebKit added support for text-decoration-skip-ink to control how underlines and overlines are rendered when they pass over glyph ascenders and descenders. WebKit previously supported this typography feature through text-decoration-skip, but since no other browsers yet support this short-hand, WebKit’s support of the long-hand will make it easier to turn off ink skipping for underlines and overlines.

    WebKit added support the ic unit, useful when typesetting CJK scripts. Much like how the ch unit is equivalent to the width (or height, whichever is the inline direction) of the 0 glyph in a font, the ic unit is equivalent to inline direction length (width or height) of the “水” glyph in the element’s current font.

    Retiring WebKit prefixes

    In an ongoing effort to reduce dependency on prefixes, WebKit newly supports several CSS properties and values that were only previous available in an earlier form. The prefixed versions will still work, now aliased to the unprefixed versions. Safari 15.4 added support for:

    • appearance, including appearance: auto
    • mask, along with the long-hand forms mask-image, mask-size, mask-repeat-x, mask-repeat-y, mask-origin
    • backface-visibility
    • text-combine-upright
    • print-color-adjust
    • match-parent CSS value for the text-align property

    WebKit also removed the non-standard CSS properties -webkit-border-fit, -webkit-margin-collapse, -webkit-margin-top-collapse, -webkit-margin-bottom-collapse, -webkit-margin-before-collapse, -webkit-margin-after-collapse, and -webkit-background-composite.

    Web APIs

    This release includes many upgrades to Web APIs in WebKit to help web developers deliver better user experiences.

    New support for BroadcastChannel allows tabs, windows, iframes, and Workers from an origin to send messages to each other. This enables experiences like syncing login state for a site across multiple tabs.

    Another new mechanism supported in WebKit is the Web Locks API to manage access to a resource as an asynchronous locking control from an origin in tabs, windows, iframes, and Workers.

    Developers can also control scroll behavior for an element with either the CSS scroll-behavior property or the behavior option in window.scroll(), window.scrollTo(), and window.scrollBy() methods in JavaScript. This new support gives developers the ability to choose between instantly jumping to a position in the viewport or smoothly animating the scroll operation.

    The ResizeObserver API has updated support for the ResizeObserverSize interface used by ResizeObserverEntry to help developers observe changes to an element’s box-sizing properties.

    The addition of structuredClone(value) provides a utility that uses the structured clone algorithm to synchronously perform a deep copy to clone and transfer objects from the input value.

    WebKit support of the File System Access API with Origin Private File System first shipped in Safari 15.2. This release introduces the getFile() method in FileSystemFileHandle making it more convenient to read a file from the file system. Plus, WebKit updated WriteableStream to work with the File System Access API. For more information, read File System Access API with Origin Private File System.


    New features and updates to JavaScript bring added convenience for developers. Handy new Array features make it nicer to search starting from the end of an array using the findLast() and findLastIndex() methods. These methods help developers avoid the typical approach requiring mutating the array with reverse() first.

    There’s also support for the at() method to access an entry at a specified integer index, which notably includes support for using negative integers to start at the end of the array.

    let list = ['banana','cherry','orange','apple','kiwi'];
    // Instead of this:
    // It's as easy as:

    The new language utility Object.hasOwn() simplifies detecting when the object has a property itself, one that is not inherited or doesn’t exist.


    As the standards process defines more Internationalization features, WebKit continues to add regular updates to its Intl implementation. This release includes identifying the supported values of local time zones, collations, calendars, numbering systems, and currency with the Intl Enumeration API.

    Intl.Locale, updated to V2, exposes new information that includes calendar-week data such as the first day of the week, text information like writing direction, and other region-dependent defaults such as calendars, 12- or 24-hour cycles, and numbering systems.

    WebKit also updated Intl.DisplayNames to V2, adding support for the calendar and dateTimeField names, and the languageDisplay option.

    The selectRange() method added to Intl.PluralRules provides locale-correct pluralization for ranges (e.g. 0-1 items). The Intl.NumberFormat V3 update adds the formatRange() and formatRangeToParts() methods for formatting a number range using locale-aware conventions along with new useGrouping, roundingPriority, roundingIncrement, trailingZeroDisplay, and signDisplay options.

    Finally, Intl.DateTimeFormat includes support for four new timeZoneName options: shortOffset, longOffset, shortGeneric, and longGeneric.

    Web Apps

    Web App Manifest and ServiceWorker received updates that improve the user experience for both websites in Safari and web apps saved to the home screen on iOS and iPadOS.

    Web App Manifest improvements include ensuring the browser always fetches the manifest file during page load instead of when the user chooses to “Add to Home Screen” from the Share menu. This approach improves reliability, and also allows a manifest file to define the characteristics of a webpage in Safari.

    In addition, declaring icons in a web app manifest file is now supported. Safari and iOS use manifest-declared icons when there is no apple-touch-icon defined in the HTML head, and when the manifest file code for declaring the icons either omits the "purpose" key or includes "purpose": "any". Defining icons by using apple-touch-icon takes precedence over manifest-declared icons in order to provide consistent behavior for web apps that use this technique to define specific icons for iOS, distinct from other mobile platforms.

    Developers can now enable Navigation Preload in ServiceWorker to improve load performance and avoid ServiceWorker startup delays that block network requests. There’s also new support for allowing users to download files generated by a ServiceWorker. WebKit also improved the reliability of using Fetch using FormData with a file going through ServiceWorker.


    The WebRTC negotiation API is now fully aligned with the WebRTC 1.0 specification to support WebRTC perfect negotiation. It is an approach that solves potential synchronization issues that can happen during negotiation between two remote peers.

    WebKit added support for in-band chapter tracks for audio and video. In-band text tracks provide captions or chapter marker information inside the container for the media itself, instead of being declared in HTML or injected with JavaScript. In-band caption tracks like CEA-608 were already supported. Now, in-band chapter tracks are also supported, where the “cue” represents the start time and title of a chapter.

    WebKit added support for requestVideoFrameCallback() on <video>, which allows the caller to be notified when there’s a new video frame available for display, and also provides metadata about that frame.


    Continuing our dedication to privacy, and to further our proposed web standard for measuring advertising in a privacy-preserving way, Safari 15.4 provides three updates to Private Click Measurement:

    • Added conversion fraud prevention via unlinkable tokens for triggering events on merchant websites.
    • Added support for same-site conversion pixels on merchant websites, to remove dependency on cross-site pixels.
    • Allowed measurement of links in nested, cross-site iframes on publisher websites.


    WebKit in Safari 15.4 improves support for Content Security Policy Level 3, providing enhanced security control over the loading of content, and helping web developers to mitigate risks of cross-site scripting and other vulnerabilities. Blocked resource violation reporting for inline script, inline style and eval execution is updated to match web standards. New support for 'strict-dynamic', 'unsafe-hashes', and 'report-sample' source expressions give developers more flexibility. Developers can also safely include external JavaScript in their pages using new support for hash source expressions.

    The release also removes support for the XSS Auditor, which has been superseded by modern cross-origin defenses like CSP and COEP.


    Developers that use WKWebView, including third-party browsers on iOS and iPadOS, can make use of new WKPreferences for additional user experience control. Apps on iOS, iPadOS, and macOS can now control allowing or preventing web content from using the Fullscreen API. Another new preference allows enabling or disabling site-specific quirks, a set of site-specific behaviors designed to improve web compatibility.

    On iPadOS, web content that uses Media Source Extensions now works in WKWebView.

    Safari Web Extensions

    In our ongoing commitment to a cross-browser interoperable model for extensions, Safari 15.4 includes additional support for Web Extensions, including support for manifest_version 3 and related API changes. New capabilities include:

    • service_worker background scripts as an alternative to non-persistent background pages.
    • Script and style injection via the browser.scripting APIs.
    • Dynamic and session rules via the browser.declarativeNetRequest APIs.
    • Webpage-to-extension messaging using externally_connectable:matches.

    And several issues were resolved, including:

    • Limits are now enforced on the size and number of items in extension sync storage.
    • More directives are now allowed to be included in the content_security_policy of an extension’s manifest, such as the sandbox directive.
    • Special matching characters (*, |, ||, and ^) in urlFilter of declarativeNetRequest rules are now handled, instead of being treated as regex patterns.
    • Promise returns from runtime.onMessage listeners are now allowed for the message reply.

    Web Inspector

    Updates to Web Inspector provide helpful new tools for working with CSS in the Styles panel, including intuitive support for Cascade Layers and the new @layer rulesets, making it easy to identify in which layer a rule is defined.

    There are also new CSS Alignment controls when using align-content, align-items, align-self, justify-content, justify-items, or justify-self for Flexbox and Grid to visually identify and select an ideal value.

    While adding new properties or values in the Styles panel, Web Inspector offers convenient auto-completion options. This release upgrades auto-completion to use fuzzy matching, making finding the right option even easier.

    When working with CSS custom properties, or CSS variables as they’re more widely known, a common practice is to add them to a selector rule for :root or html. Unfortunately, this leads to a long list of inherited CSS variables for every element on the page. Web Inspector helps you handle this in a few ways. First, it hides unused inherited CSS variables automatically. Then, a button is available to reveal them all when you need to see them. You can also use the filter tools to search for the right CSS variable. Or, you can see all applicable CSS variables grouped by value type in the Computed panel, allowing you to collapse the groups that aren’t relevant to your task.

    The list of applicable CSS variables can be grouped by value type into collapsible subsections: colors, dimensions and other types.

    Learn more by reading Taming CSS Variables with Web Inspector.


    We love hearing from you. Send a tweet to @webkit, @jensimmons or @jonathandavis to share your thoughts on this release. What technology from Safari 15.4 are you most excited about? What features or fixes do you want to see next? If you run into any issues, we welcome your feedback on the Safari UI or your WebKit bug report about web technology. Filing issues really does make a difference.

    Download the latest Safari Technology Preview to stay at the forefront of the web platform and to use the latest Web Inspector features. You can also use the WebKit Feature Status page to watch for new information about the web features that interest you the most.

    And More

    To see the full list of what’s in WebKit for Safari 15.4, including additional bug fixes, read the Safari 15.4 release notes.

    These features were first released in Safari Technology Preview: 130, 131, 132, 133, 134, 135, 136, 137, 138, and 139.

    March 14, 2022 05:35 PM

    March 04, 2022

    Release Notes for Safari Technology Preview 141

    Surfin’ Safari

    Safari Technology Preview Release 141 is now available for download for macOS Big Sur and of macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 288438-289213.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Sources Tab
      • Enabled automatic collapsing of blackboxed call frames (r288580)
    • Network Tab
      • Collapsed resource type filter scope bar into a single button to save space (r288469)
      • Changed the Ignore Caches icon to be a button with a label so it’s more visible and immediately understandable (r288533)
      • Moved the Group Media Requests and Preserve Log checkboxes into a single filter icon that shows a contextmenu with those options when clicked to save space (r288470)
    • Graphics Tab
      • Added the display of more pseudo-elements than ::before and ::after (r288623)
    • Console Tab
      • Moved the Preserve Log checkbox into a single filter icon that shows a contextmenu with those options when clicked to save space (r288702)


    • Enabled support for overflow: clip (r288973)
    • Implemented CSS overscroll-behavior for asynchronous scrolling on macOS (r288777)
    • Fixed removal of not yet loaded CSS @import (r288879)
    • Fixed CSS Grid shorthand expansion of initial values (r288544)
    • Fixed scroll-margin-top on inline elements (r288947)


    • Fixed the value not changing for stepUp() and stepDown() with out-of-range values (r289075)
    • Fixed grouping radio buttons with no form owner (r288734)
    • Used min as the default value when min is greater than max for <input type="range"> (r289209)


    • Added support for the WASM branch hinting proposal (r288758, r288761)
    • Added support for the import assertion syntax behind a flag (r288473)
    • Fixed object literal to properly resolve a name clash between an accessor and a constant property (r289166)

    Experimental Model Element

    • Allowed disabling interaction (r288728)
    • Fixed mouse interaction flipped in the y-axis (r288610)
    • Fixed <model> to not be draggable on macOS (r288723)

    Payment Request

    • Allowed additional payment method specific data to be passed to complete() (r288698)

    Web Animations

    • Fixed accelerated transform animations that start with a 1ms delay (r289211)
    • Fixed de-duplication for @keyframes rules to account for animation-composition (r288571)
    • Fixed the ability to redefine @keyframes (r288882)
    • Fixed using logical properties in the transition syntax (r289161)
    • Resolved logical properties when compiling the list of transition properties (r289167)

    Web API

    • Changed the HTMLMediaElement to dispatch the resize event asynchronously (r289108)
    • Changed to remove customElements when transitioning documents (r288450)
    • Fixed slow, CPU-bound (r288463)
    • Fixed Geolocation API to callback with an error if the document is not fully active (r288707)
    • Fixed ServiceWorkerNavigationPreloader to only be used once (r288949)
    • Implemented AbortSignal.timeout() (r289058)


    • Allowed use of hardware-fixed credentials while using “Syncing Platform Authenticator” (r289059)
    • Added authenticator attachment to PublicKeyCredential (r288622)

    Content Security Policy

    • Fixed returned WebAssembly error type when blocked (r288992)
    • Fixed blocking image content in object elements (r288792)
    • Implemented wasm-unsafe-eval (r289022)

    March 04, 2022 12:20 AM

    March 03, 2022

    Working together on Interop 2022

    Surfin’ Safari

    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 browserThe 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.


    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.


    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.


    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.

    March 03, 2022 05:00 PM

    March 02, 2022

    Taming CSS Variables with Web Inspector

    Surfin’ Safari

    CSS Custom Properties, better known as CSS variables, have been widely adopted by web designers to build reusable and configurable design systems. One common approach is to define most CSS variables in a CSS rule with a selector for a root element such as html or :root.

    While this has the benefit of putting them all in one easy to find place, it has a side effect: because CSS variables are inheritable, all descendant elements effectively inherit all variables from ancestors. This behavior is what enables you to use a CSS variable defined at a higher level on the styles of an element that is deeply nested within the document.

    Descendant elements inherit all CSS variables from their ancestor elements. This can result in very long lists of inherited properties in the Styles panel.

    When an element inherits a large number of CSS variables, inspecting its styles can become overwhelming. Likewise, identifying a particular CSS variable to reuse becomes more difficult because you have to search through a large list of them.

    Over the past few releases, Web Inspector in Safari Technology Preview has introduced some features to help you when working on projects that use large numbers of CSS variables.

    Hiding Unused CSS Variables

    Of all inherited CSS variables, only a few are actually used on the styles of any one element. To reduce visual clutter in the Styles panel, unused inherited CSS variables are automatically hidden. They’re replaced with a button that reveals them on click.

    This helps focus your attention on just the styles that took effect on the inspected element.

    Unused inherited CSS variables are automatically hidden behind a button that reveals them on click.

    Searching for CSS Variables

    The Computed panel in Web Inspector has a section that lists all CSS variables applicable to the inspected element. This list of properties can help you when searching for a CSS variable to reuse.

    Use the filter input field to narrow down the list if you know roughly what you’re looking for, either part of the CSS variable name or part of the value.

    Find all CSS variables applicable to the selected element in the Variables section of the Computed panel. Filter the list using the filter input field at the bottom of the panel.

    Grouping CSS Variables

    Safari Technology Preview 138 introduced the ability to view this list grouped by value type. This creates separate subsections for CSS variables with values such as colors, numbers, dimensions (numbers followed by CSS units), and so forth. Reduce clutter by collapsing the groups you’re not interested in.

    The list of applicable CSS variables can be grouped by value type into collapsible subsections: colors, dimensions and other types.

    Grouping this way can help you find a CSS variable when you know the type of value you’re looking for, a particular color, for example. Color swatches shown next to variable values together with the ability to group all variables with color values into one distinct section make it easier to visually scan for the desired value.

    Jump to CSS Variable Definition

    Here’s a tip: place the mouse cursor over any CSS variable in the Computed panel to reveal a go-to arrow. Click this to highlight the place in the Styles panel where the variable is defined. If the target CSS variable is hidden because it is unused, it will be automatically shown. This allows you to quickly jump in context to the place where a CSS variable is defined and edit it.

    Use the go-to arrow next to CSS variables in the Computed panel to highlight where the variable is defined in the Styles panel.

    You can also use the filter input field at the bottom of the Styles panel (as described above) and type the name of the variable, but using the go-to arrow to quickly jump to it is much more convenient.

    Fuzzy Autocompletion of CSS Variable Names

    Since its inception, Web Inspector has provided autocompletion for CSS properties and values in the Styles panel. More recently, it introduced the ability to provide completion suggestions for CSS variable names when typing within var() function values, as in var(--link-color).

    Safari Technology Preview 138 made this even better with the introduction of fuzzy matching for CSS autocompletion. This is particularly useful for CSS variables when you might not remember the full name. With fuzzy matching, you can get results that match the query at any position, not just at the beginning.

    For example, if you know that the CSS variable name you’re looking for, say --link-color, includes the substring “color”, you can type just var(color|) (the | character represents the position of the typing caret). You don’t even need to type the double dash prefix. If the CSS variable --link-color is defined on or inherited by the inspected element, it will be shown in the list of completion suggestions even if the query match occurs at the end of the variable name.

    Enjoy the flexibility of fuzzy matching for CSS autocompletion to quickly find a variable by typing any part of its name.
    For example, type just “color” to match --link-color.


    CSS variables enable a growing number of uses, such as building configurable design systems, practical theming for light and dark modes, customizable styles for web components, among many others. But the proliferation of variables in large numbers within a project can also become a burden during development and debugging.

    Web Inspector has introduced features to help keep you focused and productive. Hiding unused inherited CSS variables in the Styles panel reduces clutter. Collecting and grouping CSS variables in the Computed panel focuses your attention. Fuzzy matching in autocompletion of variable names and filter input fields help you quickly find and reuse variables.

    We hope these improvements make your work easier when dealing with CSS variables.
    As always, if you encounter any issues, please file a report at
    If you want to share feedback or ideas, please send them to us on Twitter: @webkit.

    Note: Learn more about Web Inspector from the Web Inspector Reference documentation.

    March 02, 2022 05:00 PM

    February 14, 2022

    The File System Access API with Origin Private File System

    Surfin’ Safari

    It is very common for an application to interact with local files. For example, a general workflow is opening a file, making some changes, and saving the file. For web apps, this might be hard to implement. It is possible to simulate the file operations using IndexedDB API, an HTML input element with the file type, an HTML anchor element with the download attribute, etc, but that would require a good understanding of these standards and careful design for a good user experience. Also, the performance may not be satisfactory for frequent operations and large files.

    The File System Access API makes it possible for web apps to have easy and efficient file access. It provides a way to create, open, read, and write files directly. It also allows apps to create directories and enumerate their contents.

    Origin Private File System

    WebKit has added support for the File System Access API with the origin private file system — a private storage endpoint to some origin. Conceptually, every origin owns an independent directory, and a page can only access files or directories in its origin’s directory. For example, cannot read files created by

    Based on the implementation of different browsers, one entry in the origin private file system does not necessarily map to an entry in the user’s local filesystem — it can be an object stored in some database. That means a file or directory created via the File System Access API may not be easily retrieved from outside of the browser.


    The API is currently unavailable for Safari windows in Private Browsing mode. For where is it available, its storage lifetime is the same as other persistent storage types like IndexedDB and LocalStorage. The storage policy will conform to the Storage Standard. Safari users can view and delete file system storage for a site via Preferences on macOS or Settings on iOS.

    Browser Support

    The File System Access API with origin private file system is enabled in WebKit from r284131. It is available in Safari on:

    • macOS 12.2 and above
    • iOS 15.2 and above

    In Safari on macOS 12.4 and iOS 15.4, we introduced the getFile() method of FileSystemFileHandle.

    The API

    WebKit currently supports four interfaces of the File System Access API:

    • FileSystemHandle, which represents an entry in the file system. It is available in Worker and
    • FileSystemFileHandle, which inherits from FileSystemHandle and represents a file entry.
    • FileSystemDirectoryHandle, which inherits from FileSystemHandle and represents a directory entry.
    • FileSystemSyncAccessHandle, which provides an exclusive duplex stream for synchronous read and write on an entry. Unlike the interfaces above, which exist in both Window and Worker contexts, FileSystemSyncAccessHandle is only available in Worker.

    With these basic interfaces in mind, let’s look at how to use them by diving into some examples.


    Accessing the Origin Private File System

    In the origin private file system, a FileSystemHandle represents either the root directory of the origin’s space, or a descendant of the root directory. Therefore, the first step is to get the root FileSystemDirectoryHandle. It is done via StorageManager interface.

    const root = await;

    Creating a directory or a file

    With a FileSystemDirectoryHandle object like root, you can get access to its child with some specific name using getDirectoryHandle() and getFileHandle() methods.

    // Create a file named Untiled.txt under root directory.
    const untitledFile = await root.getFileHandle("Untitled.txt", { "create" : true });
    // Get access to existing Untitled.txt file.
    // untitledFile and existingUntitledFile point to the same entry.
    const existingUntitledFile = await root.getFileHandle("Untitled.txt");
    // Create a directory named Diary Folder.
    const diaryDirectory = await root.getDirectoryHandle("Diary Folder", { "create" : true });

    Moving or Renaming a Directory or a File

    To move around the file or directory a FileSystemHandle represents, you can use the move() method. The first parameter is a FileSystemDirectoryHandle representing the target parent directory, and the second parameter is a USVString representing the target file name. The string must be a valid file name.

    // Move Untitled.txt from /root/ to /root/Diary Folder/.
    await untitledFile.move(diaryDirectory,;
    // Rename Untitled.txt to Feb_01.txt
    await untitledFile.move(diaryDirectory, "Feb_01.txt");
    // The two steps above can be combined as:
    // await untitledFile.move(diaryDirectory, "Feb_01.txt");

    Resolving the Path from a Directory Entry to its Descendant

    To find out if a FileSystemHandle is a descendant of an existing FileSystemDirectoryHandle, and to get their relative path, you can use the resolve() method. The result is an array of component names that forms the path.

    // Get access to Feb_01.txt in Diary Folder.
    const diaryFile = await diaryDirectory.getFileHandle("Feb_01.txt");
    // Resolve path between Feb_01.txt and root.
    const relativePath = await root.resolve(diaryFile);
    // relativePath is ["Diary Folder", "Feb_01.txt"].

    Enumerating Contents in a Directory

    The methods introduced above require you to know the name of target, but if you don’t know the name, you can still get it by enumerating the contents of an existing directory with async iterators returned by the keys(), values(), and entries() methods.

    // Create a directory named Trash under the root directory.
    const trashDirectory = await root.getDirectoryHandle("Trash", { "create" : true });
    // Find directories under root/ and print their names.
    const directoryNames = [];
    for await (const handle of root.values()) {
        if (handle.kind == "directory") {
    // directoryNames is ["Trash", "Diary Folder"].

    Deleting a Directory or a File

    With a FileSystemDirectoryHandle object, you can delete its child entries by name with the removeEntry() method.

    // Delete Feb_01.txt in Diary Folder.
    await diaryDirectory.removeEntry(;
    // Delete Trash and all its descendants.
    await root.removeEntry(, { "recursive" : true });

    Reading a File

    Once you have the FileSystemFileHandle representing the target file, you can read its properties and content by converting it to a File object using the getFile() method. You can get file information and content using interfaces of File.

    const fileHandle = await root.getFileHandle("Draft.txt", { "create" : true });
    const file = await fileHandle.getFile();

    Reading and Writing a File in a Worker Thread

    Another way to read a file is to use the read() method of the FileSystemSyncAccessHandle interface. You can create a FileSystemSyncAccessHandle from a FileSystemFileHandle object using the createSyncAccessHandle() method. Since FileSystemSyncAccessHandle is only available in Worker contexts, you will need to create a dedicated Worker first.

    Unlike getFile() that returns a Promise, read() is synchronous, and thus provides better performance. If you’re aiming for the most efficient file access, FileSystemSyncAccessHandle is the way to go.

    To write a file, you can use the synchronous write() method of FileSystemSyncAccessHandle. In the current implementation, this is the only way to write a file in WebKit.

    To implement synchronous read and write operations, a FileSystemSyncAccessHandle must have exclusive access to a file entry. Therefore, the attempt to create a second FileSystemSyncAccessHandle on an entry will fail, if the previous FileSystemSyncAccessHandle is not closed properly.

    // Get access to the existing Draft.txt file.
    const root = await;
    const draftFile = await root.getFileHandle("Draft.txt");
    // Create FileSystemSyncAccessHandle on the file.
    const accessHandle = await draftFile.createSyncAccessHandle();
    // Get size of the file.
    const fileSize = await accessHandle.getSize();
    // Read file content to a buffer.
    const readBuffer = new ArrayBuffer(fileSize);
    const readSize =, { "at": 0 });
    // Write a sentence to the end of the file.
    const encoder = new TextEncoder();
    const writeBuffer = encoder.encode("Thank you for reading this.");
    const writeSize = accessHandle.write(writeBuffer, { "at" : readSize });
    // Truncate file to 1 byte.
    await accessHandle.truncate(1);
    // Persist changes to disk.
    await accessHandle.flush();
    // Always close FileSystemSyncAccessHandle if done.
    await accessHandle.close();


    If your web app needs to interact with files, you should try the new File System Access API. It provides interfaces that are similar to the native file system API, with optimized performance.

    As the standard evolves and development goes on, we will keep adding or updating interfaces and methods according to the File System Access API spec. If you encounter any issue when using this API, please file a bug on under the “Website Storage” component. You may also create a new bug report for feature requests, describing your use case and why the feature is important. If you have any question or suggestion about the API itself, you can file a spec issue in the WICG repo. Your feedback is very important to us.

    February 14, 2022 05:00 PM

    February 10, 2022

    Release Notes for Safari Technology Preview 140

    Surfin’ Safari

    Safari Technology Preview Release 140 is now available for download for macOS Big Sur and of macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 287834-288438.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Elements Tab
      • Made CSS Alignment controls in the Styles Detail Sidebar accessible (r288385)
    • Sources Tab
      • Added a contextual menu item to create a URL Breakpoint for resources initiated by script (r288029)
      • Fixed fully blackboxed stack traces to show the right top call frame (r288266)
    • Timelines Tab
      • Added better names for newer CSS Animations, CSS Transitions, and Web Animations events (r287945)

    :has() Pseudo-Class

    • Added support for :has(:not(foo)) (r288303)
    • Avoided complex style invalidation with repeated DOM mutations (r288012, r287973)
    • Fixed computing specificity (r288196)
    • Changed to disallow nested :has() (r288111)
    • Changed to ignore :visited inside :has() (r288304)


    • Added support for intrinsic sizes in flex-basis (r288113)
    • Added support for intrinsic sizes to the flex shorthand (r288184)
    • Added support for “missing“/”none” color components (r288143)
    • Added support for interpolating colors with “missing”/“none” components via color-mix() (r288427)
    • Added support for preloading of layered @import rules (r288099)
    • Changed interpolation mode for CSS gradients to default to OKLab if any non-legacy color syntax colors are used in the stops (r288071)
    • Changed to only apply automatic minimum block-size aspect-ratio rules to non-replaced elements (r288003)
    • Fixed the ::backdrop pseudo-element to react to associated element event listeners (r287878)
    • Fixed the CSS color() function to not clamp channels to the 0-1 range (r287838)
    • Fixed the height of flex items with aspect-ratio whenever the cross axis intrinsic size is larger than the viewport (r287976)
    • Fixed rounding of distributed free space to flexible tracks (r287977)
    • Fixed position: fixed layers to not allocate a backing buffer if all the children are offscreen (r288429)
    • Fixed setting content: normal on a ::marker to make the computed style return resolved values (r288054)

    Web API

    • Added support for FetchEvent.handled API for Service Workers (r287915)
    • Enabled form.requestSubmit() (r288179)
    • Fixed nextHopProtocol exposed regardless of Timing-Allow-Origin (r288219)
    • Fixed input.labels inside shadow DOM (r288162)
    • Fixed canvas functions that take colors as strings to support all the syntax that CSS supports (r288134)
    • Implemented HTMLScriptElement.supports(type) method (r287996)
    • Improved computation of Service Worker FetchEvent.resultingClientId (r288201)

    Web Animations

    • Added support for animation-composition CSS property (r288433)
    • Changed getKeyframes() for a CSS Animation to not use computed style for keyframes (r287835)
    • Fixed interpolation during animation of two empty transform lists to always yield “none” (r287917)


    • Fixed Date functions’ argument coercion (r288066)
    • Relaxed Date.parse requirement (r288411)


    • Fixed misc WebAssembly.Table issues (r288064)
    • Fixed misc issues in WebAssembly.Exception (r288065)
    • Fixed WebAssembly.Global‘s typename for “anyfunc” (r288049)

    Dialog Element

    • Added visibility: visible to modal dialogs in the user-agent stylesheet (r288233)
    • Fixed some overflow and clipping issues with modal dialogs (r288267, r287845)


    • Fixed support for new lines in HTMLTextArea’s placeholder text (r288005)
    • Fixed keeping the selected state of a select element when inserting a selected <option> (r288174)
    • Prevented contenteditable anchors from being stuck (r288420)


    • Fixed gl.texImage2D upload of getUserMedia streams via <video> element fails (r288025)
    • Fixed MediaStream canvas.captureStream() playback (r288435)
    • Fixed portrait video playback on HTML canvas elements (r288053)
    • Fixed no VP9-SVS video stream from remote peer on some devices (r287928)
    • Fixed “Add to Contact” menu item (r287959)
    • Tightened the focus check for getUserMedia (r288087)


    • Allowed single use of WebAuthn without user gesture for all relying parties (r287957)


    • Exposed toggle buttons using role="button" as form controls (r288100)
    • Improved support for aria-owns in ARIA trees (r288117)

    Content Security Policy

    • Improved handling of multiple policies (r288132)


    • Fixed an issue where a website may be able to track sensitive user information (r288078)


    • Fixed calculations of filterRegion and outsets of the referenced SVG filter (r288183)
    • Fixed referenced SVG filter always using sRGB color space for its result (r287982)


    • Fixed sometimes not being able to scroll after using a builtin trackpad (r287997)

    Web Extensions

    • Fixed a crash when calling browser.webNavigation.getAllFrames() on an empty tab
    • Fixed Active tab permissions for a tab to get removed if a matching per-site permission changes to “Deny”
    • Fixed service worker background script errors to get reset each time they successfully load

    February 10, 2022 09:05 PM

    February 07, 2022

    Introducing the Dialog Element

    Surfin’ Safari

    Although the alert, confirm and prompt JavaScript methods are convenient, they aren’t recommended due to their script-blocking behavior. That’s why we worked with other browser vendors to drive improvements to the <dialog> specification over the last few years. The most important conversations involved accessibility.

    You can find more complex use-cases, like payment dialogs, on the web. They are currently addressed by custom solutions from frameworks like Bootstrap. Unfortunately, they aren’t convenient to use and aren’t always accessible. We believe the web deserves a simple and bug-free solution for these use-cases. Safari Technology Preview 134 and Safari 15.4 beta introduces the <dialog> element for this reason!

    How Do I Use <dialog>?

    Let’s create a simple confirmation dialog:

    <dialog id="confirmation-dialog">
        <h1>Do you want to delete everything?</h1>
        <p>You will lose all your data.</p>
        <button id="cancel-delete">Cancel</button>
        <button id="confirm-delete">Delete!</button>

    Dialogs are hidden by default. We can use the showModal() method to show the dialog. When it’s shown, the dialog can be closed with the close() method.

    Here is an example:

    <button id="delete">Delete everything</button>
    <p id="result"></p>
    let dialog = document.getElementById("confirmation-dialog");
    let result = document.getElementById("result");
    // Show the dialog when clicking "Delete everything"
    document.getElementById("delete").addEventListener("click", function() {
    document.getElementById("cancel-delete").addEventListener("click", function() {
        result.textContent = "Canceled!";
    document.getElementById("confirm-delete").addEventListener("click", function() {
        result.textContent = "Deleted!";

    Note that the dialog will get an open attribute once opened, which may be useful for styling purposes. However, it’s not recommended to toggle this attribute manually to show or hide the dialog, since the browser may lose track of the dialog state, and will not perform proper focus adjustments for accessibility.

    Example confirmation dialog

    Modal and Non-modal Dialogs

    In the last example, the showModal() method was used to create a modal dialog. User interaction is locked inside modal dialogs and outside content cannot be clicked, focused, selected, edited, or seen by accessibility tools. Another feature of modal dialogs is their ability to appear on top of everything else in the web page, regardless of the z-index of other elements.

    Non-modal dialogs also exist and can be invoked using show() method. Unlike modal dialogs, they still allow interaction with the surrounding content. An example use-case may be a find-in-page dialog for a document editor, where you still want to allow the user to interact with the rest of the document.

    Using Forms with <dialog>

    Forms within dialogs can be used to request information from the user, such as when a shipping address or payment details are needed.

    Unlike a traditional <form>, where method="get" or "post" indicates that the form data is sent to a server, using <form method="dialog"> causes form submission instead to close the dialog and set the returnValue property to the submit button’s value. This can save you from writing custom code, while providing the correct semantics to your web page.

    We can simplify the initial example using this new feature:

    <dialog id="confirmation-dialog">
        <form method="dialog">
            <h1>Do you want to delete everything?</h1>
            <p>You will lose all your data.</p>
            <button type="submit" value="Canceled!">Cancel</button>
            <button type="submit" value="Deleted!">Delete!</button>
    <button id="delete">Delete everything</button>
    <p id="result"></p>
    let dialog = document.getElementById("confirmation-dialog");
    document.getElementById("delete").addEventListener("click", function() {
    dialog.addEventListener("close", function() {
        document.getElementById("result").textContent = dialog.returnValue;

    Note the use of the close event here, which is special to <dialog>.

    Example confirmation dialog with a form


    The semi-transparent box behind the dialog that you may have noticed from previous examples is the ::backdrop pseudo-element. By default, it is styled so it covers the whole viewport. Like the dialog itself, you can style the backdrop using CSS. Animations can also be used if you would like to add a fade-in effect for instance.

    Note that the backdrop is only shown for modal dialogs.

    Here is an example:

        <h1>This is a pretty dialog</h1>
        <p>The backdrop animates!</p>
    <button onclick="document.querySelector('dialog').showModal()">Show the dialog</button>
    dialog {
        box-shadow: 0 2px 5px rgba(0,0,0,0.3);
        border: none;
        border-radius: 10px;
    dialog::backdrop {
        background: linear-gradient(rgba(0,0,0,0.1), rgba(0,0,0,0.4));
        animation: fade-in 1s;
    @keyframes fade-in {
        from {
            opacity: 0;
        to {
            opacity: 1;

    This is a pretty dialog

    The backdrop animates!

    Pretty dialog example styled and animated


    For accessibility tools, the <dialog> element is equivalent to role="dialog". In addition to that, a modal dialog will behave similarly to an element with aria-modal="true".

    Users can dismiss modal dialogs using the “Escape” key on desktop browsers. That will trigger a cancel event which you can intercept. If multiple modal dialogs are opened, the one shown last will be dismissed.

    It is also possible to specify an element to initially focus on when opening dialogs by adding the autofocus attribute to the relevant element.

    Browser Support

    Next Steps

    In this post, we’ve covered the basics of <dialog> and the features around it. Here are the next steps around this element:

    We are working on getting the element interoperable with other browser vendors as part of the Interop 2022 effort. One of the main discussions is around initial focus behaviour, to agree on which elements should be focused by default when there is no element with the autofocus attribute.

    As part of implementing <dialog> we’ve also made advances on the inert attribute to get interoperable behavior across browsers. It is currently disabled by default and not yet standardized, but you can enable the “inert attribute” in the Experimental Features menu from the Develop menu in Safari Technology Preview to test it.

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

    February 07, 2022 07:10 PM

    January 31, 2022

    The Focus-Indicated Pseudo-class :focus-visible

    Surfin’ Safari

    The “focus indicator”, as its name suggests, visually indicates (often with a kind of an outline) that an element has focus. That sounds simple enough, and it perfectly describes what the old :focus selector makes possible. However, for decades now, whether a browser will actually display a focus indicator natively has been a considerably more complex affair.

    Focus indicator on different elements in SafariFocus indicator on different elements in Safari

    Based on lots of feedback and study, browsers have long employed heuristics about both the type of element, and how it came to gain focus, in order to determine whether the focus indicator should be displayed. If the user is navigating the page with the keyboard, it should. In other situations, it depends. A text input, for example, will display an indicator regardless of how it received focus. This is important because all users need to know where their input data will be placed. Interfaces that do not employ those heuristics feel unnatural.

    The goal of the old :focus selector was to allow authors to better style the focus indicator to be in tune with their overall design choices. The trouble is that using it meant losing the heuristics. The net result, unfortunately, has been that the most common use of the :focus selector has been to remove indicators altogether. This avoids the “false positive” focus styles that cause complaints from many users. The problem is that removing focus styling breaks website accessibility, causing trouble for people navigating the page using the keyboard.

    Fortunately, a new CSS selector comes to the rescue, avoiding this kind of accessibility issue while providing the behavior web developers were looking for. The :focus-visible pseudo-class matches elements based on the browsers heuristics. It allows web authors to style the focus indicator only if it would be drawn natively.

    Despite being a new feature that has recently landed on the web platform, it’s already being used by almost 1% of web pages (according to the Web Almanac by HTTP Archive).


    As of Safari Technology Preview 138, the :focus-visible selector has been added to WebKit, paying special attention to interoperability with other implementations. As part of the WebKit implementation, the Web Platform Tests test suite has been improved and expanded quite a lot, adding coverage to new cases and ensuring a better interop between the different implementations.

    This work led to changes and improvements all around. Thanks to tests and discussions, all browsers now follow a common set of heuristics and have the same behavior in most situations. In WebKit, for example, clicking on a <div tabindex="0"> will no longer show a focus indicator by default (matching other browser engines).

    In addition, the default User Agent style sheet in all browsers now uses the :focus-visible pseudo-class. This is a nice thing to have because it avoids the type of issues that happened with :focus in the first place, and circumvents the need of using some weird workarounds, like :focus:not(:focus-visible), to style the default focus indicator painted by the browser.

    If you’re curious about the implementation details, you can read a series of blog posts written along the path to development at, or watch this talk.


    The good news after all the changes that have happened around :focus-visible lately, is that now you’d just need to use :focus-visible selector to style the focus indicator drawn by the browser engines.

    On top of that, browsers are no longer showing a focus indicator when the user isn’t expecting it, like focusing a regular element via mouse click.

    Web authors won’t need any kind of workaround to achieve the goal of styling the focus indicator shown by the web engine.

    For example, if you want your focus indicator to have a thick magenta outline with a small offset, you just need to use the following CSS on your website:

    :focus-visible {
        outline: solid thick magenta;
        outline-offset: 0.1em;
    Customized focus indicator outline using :focus-visible

    And you can use other properties, not only the outline related ones, to customize the focus indicator in your website. E.g.:

    :focus-visible {
        outline: dotted thick green;
        background: lime;
        box-shadow: 0.3em 0.3em lightgrey;
    Customized focus indicator changing outline, background and box-shadow with :focus-visible

    Open Prioritization

    It’s worth mentioning that this work was collectively chosen from among many, in numerous engines and partially funded by the public, through Igalia’s Open Prioritization campaign. This effort works to democratize the development of web platform features, giving groups of people and smaller organizations the chance to have a direct impact on the web ecosystem. If you want to learn more about this, Eric Meyer gave a talk explaining it at the W3C’s TPAC.

    Thanks to many contributions, this experiment yielded not only :focus-visible development in WebKit, but a lot of interoperability work and alignment in other implementations and a sense that there was real demand for the feature. Thank you to everyone involved.


    :focus-visible pseudo-class has been enabled by default in Safari Technology Preview 138, please try it out and report any issues you might found on You can also send a tweet to @regocas or @webkit to share your thoughts about this feature.

    January 31, 2022 05:00 PM

    January 26, 2022

    Release Notes for Safari Technology Preview 139

    Surfin’ Safari

    Safari Technology Preview Release 139 is now available for download for macOS Big Sur and macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 286944-287834.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Elements Tab
      • Added revert-layer to CSS autocompletion (r287636)
      • Supported CSS conic gradients in gradient editor and auto-completion (r287409)
      • Made gradient editor angle input readable in dark mode (r287408)
    • Sources Tab
      • Added icon for collapsed blackboxed section (r287586)
      • Fixed expanding a grouping of blackboxed call frames to be persistent (r287590)


    • Fixed CSS Cascade Layers specified in import rules can not be reordered on media query evaluation (r286972)
    • Fixed revert-layer to revert the style attribute to the regular author style for CSS Cascade Layers (r287018)
    • Fixed absolutely positioned children to be aligned using the margin box inside a flexbox (r287064)
    • Used the correct margins in computeInlinePreferredLogicalWidths in orthogonal flows (r286952)
    • Fixed :focus-visible not matching on accessKey focus after focusing something via mouse (r287563, r287662)
    • Fixed flexbox ignoring margins of absolute positioned children when align-items: flex-end or justify-content: flex-end (r287128)
    • Fixed drop-shadow filter to use the value of the color property when it has no specified color (r287817)
    • Fixed transform property to take into account transform reference box (r287606)
    • Fixed inline blocks that contain text with min-width, box-sizing: border-box to include the border in width calculation (r287779)
    • Fixed text-decoration color not changing back after input blur with outline removed (r287674)
    • Implemented text-combine-upright property (standard version of -webkit-text-combine) (r287487)
      • Made -webkit-text-combine an inherited property (r287451)
    • Unprefixed -webkit-print-color-adjust CSS property (r287712)
    • Removed non-standard -webkit-background-composite property (r287433)
    • Removed non-standard -webkit-margin-*-collapse properties (r287429)


    • Added style invalidation for :disabled, :enabled, :valid, and :invalid (r287445, r287551)
    • Fixed :has() matching wrong elements due to style sharing (r287362)
    • Fixed :has() selector invalidation issue with toggling :checked (r287363)
    • Used a bloom filter to quickly reject :has() selectors (r287091)

    Web Animations

    • Added support for the animation shorthand property in the computed style (r287535)
    • Changed reversing factor to be computed before canceling the previous transition (r287548)
    • Changed transitions without an explicit transition-property to not be considered (r287764)
    • Fixed animation shorthand to parse values in the right order (r287509)
    • Fixed animation shorthand to list all longhand values when serializing (r287534)
    • Fixed calling setKeyframes() on a running CSS Transition having no immediate effect (r287549)
    • Fixed changing the effect of a transition to no longer mark it as running (r287550)
    • Fixed getKeyframes() for a CSS Animation to not use computed style for keyframes (r287820)
    • Fixed getKeyframes() to ensure that all properties are present on 0% and 100% keyframes (r287518)
    • Fixed getKeyframes() to return an empty object when there are no animatable properties in @keyframes rule (r287517)
    • Fixed inserting a rule within a @keyframes rule should update animations (r287707)
    • Fixed inserting a new @keyframes rule to start animations that already used this name (r287769)
    • Fixed implicit keyframe for a CSS Animation to always use the underlying style (r287827)
    • Fixed interpolation for the filter property failing with a single keyframe (r287826)
    • Fixed translate() function in the transform property to remove trailing 0 value when parsing (r287822)


    • Changed to pre-layout orthogonal children to compute the preferred logical width inside a flexbox (r287263)
    • Fixed paint order of CSS text decorations (r286955)
    • Fixed incorrect percent-based height inside display: table child elements defined when combined with box-sizing: border-box and padding. (r287063)
    • Fixed aspect-ratio size calculation (r287023)


    • Fixed SVG resource invalidation logic causing incorrect layout state (r287076)


    • Changed to allow get() for a same-site, cross-origin iframe (r286993, r287116)
    • Fixed authenticator to fallback to clientPIN after internal verification fails and is blocked (r287315)

    Web API

    • Added support for using a user-specified preference before using the system’s preferred color scheme (r287030)
    • Fixed FormData constructed in the form’s submit event listener to not include the submitter (r286988)
    • Fixed TextDecoder to detect invalid UTF-8 sequences early enough (r287024)


    • Aligned Array.prototype.toLocaleString to ECMA402 definition (r287560)
    • Fixed Intl.PluralRules.selectRange input validation (r287546)
    • Fixed length of Intl.NumberFormat.formatRange and Intl.PluralRules.selectRange (r287543)

    Content Security Policy

    • Changed to always use UTF-8 encoded content when checking hashes (r287270)
    • Implemented CSP strict-dynamic for module scripts (r287756)


    • Added MediaRecorder support for the bitsPerSecond option (r287613)
    • Changed to prevent packing audio samples with discontinuity together (r287249)

    Service Workers

    • Added full support for Service Worker interception of fetch requests with FormData body (r287612)
    • Fixed FetchRequest.clone to not need to be called with the current context (r287532)

    Web Extensions

    • Added support for the redirect rule type in declarativeNetRequest, which requires host permissions to be granted for the host of the URL being redirected
    • Added support for declarativeNetRequest.getMatchedRules, which requires host permissions to be granted to view the URLs of blocked resources

    January 26, 2022 10:22 PM

    January 20, 2022

    Release Notes for Safari Technology Preview 138

    Surfin’ Safari

    Safari Technology Preview Release 138 is now available for download for macOS Big Sur and macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 286534-286944.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Elements Tab
      • Added support for Cascade Layers in the Styles sidebar (r286558)
      • Added a swatch for align-items and align-self (r286875)
      • Added a swatch for justify-content, justify-items, and justify-self (r286885)
      • Added CSS variable names to property name completion list (r286890)
      • Added an option to group CSS variables by value type in the Computed sidebar (r286876)
      • Enabled fuzzy matching for CSS completions in the Styles sidebar (r286792, r286611)


    • Enabled :focus-visible pseudo-class by default (r286783, r286776, r286775)
    • Enabled the resolution media query by default (r286874)
    • Enabled the CSS Contain property by default (r286828)
    • Changed to account for captions when flexing tables with specified sizes (r286593)
    • Fixed perspective() less than or equal to 1px to be clamped to 1px (r286591)
    • Fixed gap to work correctly when flex-direction: column-reverse is applied (r286654)
    • Fixed the serialization of CSSImportRule (r286668)
    • Unprefixed -webkit-mask (r286795)
    • Unprefixed CSS value text-align: -webkit-match-parent (r286803)
    • Updated color-mix() to support srgb-linear and alpha premultiplication (r286568)

    Experimental Model Element

    • Added load and error events to distinguish resource load from model readiness (r286836)


    • Fixed various issues with complicated rendering of VTT cues (r286743)

    Web Animations

    • Added a way to run scripted animations via CustomEffect (r286555)
    • Exposed a frameRate property to Web Animations (r286915)


    • Improved WebAuthn Level 2 standards compliance by supporting the displayName (r286746)


    • Fixed transform-origin on SVG elements to take into account the transform reference box origin (r286942)

    Web API

    • Changed to group radio buttons with no form owner (r286855)
    • Fixed the range of <input type="time"> to be reversible (r286581)
    • Fixed an <input> that’s been autofilled with obscured content to still be editable (r286814)
    • Implemented AbortSignal.throwIfAborted (r286904)
    • Improved <input type="datetime-local"> value parsing and sanitization (r286869)
    • Restored navigator.hardwareConcurrency (r286550)
    • Fixed setting onselectionchange content attribute to add an event listener (r286898)

    Service Workers

    • Enabled NavigationPreloadManager by default (r286540)
    • Added support for ServiceWorker downloads (r286944)
    • Fixed “no-cache” network error (r286655)
    • Fixed same-site lax cookies not sent by fetch event handler after page reload (r286656)


    • Closed WebRTC allocation sequence shared socket in case of sequence network failure (r286539)


    • Added preparation of WebAssembly.Memory imports in Wasm/ESM modules (r286703)

    Web Extensions

    • Added support for changed web_accessible_resources declaration with manifest_version 3
    • Fixed runtime.onMessage listeners not supporting Promise returns for the reply message
    • Show error messages for incorrect match patterns in web_accessible_resources
    • Show error message to devtools tabs in Web Inspector when inspecting unsupported targets
    • Show error message when the service worker background script fails to load

    Bug Fixes

    • Fixed an issue introduced in Safari Technology Preview 137 where pop-up windows wouldn’t open

    January 20, 2022 11:39 PM

    December 28, 2021

    Manuel Rego: A story on web engines interoperability related to wavy text decorations

    Igalia WebKit

    Some weeks ago I wrote a twitter thread explaining the process of fixing a Chromium bug related to wavy text decorations. At first it was just a patch in Chromium, but we also fixed the same issue in WebKit, which unveiled a mistake on the initial fix in Chromium (a win-win situation).

    This blog post is somehow a story around web platform features implementation, which highlights the importance of interoperability and how investigating and fixing bugs on different web engines usually leads to gains for the whole ecosystem.

    Some background

    Let’s start from the beginning. Igalia (as part of our collaboration with Bloomberg) is working on adding support for ::spelling-error & ::grammar-error highlight pseudo-elements in Chromium.

    My colleague Delan Azabani has been leading this effort. If you want more details about this work you can read her two blog posts. Also don’t miss the chance to enjoy her amazing talk from last BlinkOn 15, this talk gives lots of details about how highlight pseudos work, and includes some cool animations that help to understand this complex topic.

    Lately I’ve been also helping with some related tasks here and there. Next I’m going to talk about one of them.

    Spelling and grammar error markers

    As you probably know spelling and grammar error markers use wavy underlines in some platforms like Linux or Windows, though not in all of them as they use dotted underlines on Mac. In Chromium they’re painted on a separated codepath, totally independent of how CSS text decorations are painted. You can easily spot the difference between a “native” spelling errors (left) and elements with text-decoration: wavy red underline (right) in the next picture.

    Spelling errors on the left vs wavy red underlines on the right Spelling errors Chromium Linux (left) vs wavy red underlines (right)

    As part of our work around ::spelling|grammar-error highlight pseudos, we plan to merge both codepaths and use the CSS one for painting the default spelling and grammar error markers in the future. This doesn’t mean that they’ll look the same, actually they will still have a different rendering so the user can differentiate between a spelling marker and a wavy text decoration (like it happens now). But they’ll share the same code, so any improvement we do will apply to both of them.

    There have been some bugs on each of them in the past, related to invalidation and overflow issues, and they had to be fixed in two places instead of just one. That’s why we’re looking into sharing the code, as its main job is to produce very similar things.

    The issue we’re describing in the next section doesn’t happen on native spelling error markers, but as we plan to follow the CSS codepath, we had to fix it as a preliminary task getting things ready to move the spelling markers to use that codepath.

    The issue

    One problem with wavy text decorations in Chromium was that they sometimes don’t cover the full length of the text. This is because it only paints whole cycles and thus fall short in some situations.

    A simple example is a wavy underline (text-decoration: wavy green underline) on a “m” letter using big fonts (see the picture below and how it doesn’t cover the full length of the letter).

      <div style="font-size: 5em; text-decoration: wavy green underline;">m</div>

    Green wavy underline doesn't cover the full length of the letter m (Chromium) Green wavy underline doesn’t cover the full length of the letter “m” (Chromium)

    Fixing the problem

    This section goes into some implementation details about how this works on Chromium and how we fixed it.

    To draw wavy text decorations Chromium defines a vector path for a Bezier curve, that path is generated at TextDecorationInfo::PrepareWavyStrokePath(). The comment in that method is quite self explanatory: Comment from TextDecorationInfo::PrepareWavyStrokePath() explaining how the path for the Bezier curve is defined Comment from TextDecorationInfo::PrepareWavyStrokePath() explaining how the Bezier curve is defined

    This method generates the path for wavy text decorations using the next loop:

        for (float x = x1; x + 2 * step <= x2;) {
          control_point1.set_x(x + step);
          control_point2.set_x(x + step);
          x += 2 * step;
          path.AddBezierCurveTo(control_point1, control_point2,
                                gfx::PointF(x, y_axis));

    As you can see, it only uses whole cycles of the wave (2 * step), and it never splits that in smaller chunks. If we’re going to end up further away than the text size, we don’t add that wave to the path (x + 2 * step <= x2). Which leads to the wrong behavior we saw in the example of the “m” letter above, where the text decoration falls short.

    To prevent this problem, the code was using the method AdjustStepToDecorationLength(), that was expected to adjust the length of a whole wave. If that method was working properly, we would always cover the full text width adjusting the size of the waves. However there were two different problems on that method:

    • On one side, that method adjusted the step, but we were always generating whole waves (2 * step), so we might need to adjust the whole length of the wave instead.
    • On the other side, the method had some bug, as it was changing the step when it was not actually needed. For example if you pass a total length of 40px and a step of 10px, this method was adjusting the step to 10.75px, which makes no sense.

    Digging a little bit on the repository’s history, we found out that this method has been around since 2013 thus it was present in both Blink and WebKit. As it has a bunch of issues and our proposed fix was cutting the waves at any point, we decided it was not needed to try to adjust their size anymore, so we get rid of this method.

    The solution we used to the length issue requires two main changes:

    • First we generate two extra waves before and after the text width:
      // We paint the wave before and after the text line (to cover the whole length
      // of the line) and then we clip it at
      // AppliedDecorationPainter::StrokeWavyTextDecoration().
      // Offset the start point, so the beizer curve starts before the current line,
      // that way we can clip it exactly the same way in both ends.
      FloatPoint p1(start_point + FloatPoint(-2 * step, wave_offset));
      // Increase the width including the previous offset, plus an extra wave to be
      // painted after the line.
      FloatPoint p2(start_point + FloatPoint(width_ + 4 * step, wave_offset));
    • Then clip the path so it’s no longer than the text width. For which GraphicsContextStateSaver was really useful to just clip things related to the line that is currently being painted (for example in cases where you have both underline and overline text decorations).

    Video showing the solution described above

    The reviewers liked the idea and the patch landed in Chromium 97.0.4692 with some internal tests (not using WPT tests as how wavy lines are painted is not defined per spec and varies between implementations).

    To finish this section, below there is a screenshot of the “m” with wavy green underline after this patch.

    Green wavy underline covering the full length of the letter m (Chromium) Green wavy underline covering the full length of the letter “m” (Chromium)

    WebKit & WPT test

    While looking into the history of AdjustStepToDecorationLength() method we ended up looking into some old WebKit patches, we realized that the code for the wavy text decoration in WebKit is still very similar to Chromium, and that this very same issue was also present in WebKit. For that reason we decided to fix this problem also in WebKit too, with the same approach than the patch in Chromium.

    Green wavy underline doesn't cover the full length of the letter m (WebKit) Green wavy underline doesn’t cover the full length of the letter “m” (WebKit)

    The cool thing is that during the patch review Myles Maxfield suggested to create a mismatch reference test.

    Just an aside quick explanation, reference tests (reftests) usually compare a screenshot of the test with a screenshot of the reference file, to see if the rendered output matches exactly or not between both files. But sometimes browsers do a different thing that is called mismatch reftests, which compares a test with a reference and checks that they’re actually different.

    The idea here was to do a test that has a wavy text decoration but we hide most of the content with some element on top of that, and just show the bottom right corner of the decoration. We mismatch against a blank page, because there should be something painted there, if the wavy text decoration cover the whole line.

    So we wrote WPT tests, that we can share between implementations, to check that this was working as expected. And while working on that test we discovered an issue on the initial Chromium fix, as the wavy underline was kind of misplaced to the left. More about that later.

    On top of that there was another issue, WPT mismatch tests were not supported by WebKit tests importer, so we also added support for that in order to be able to use these new tests on the final WebKit patch fixing the wavy text decorations length which is included in Safari Technology Preview 136.

    Again let’s finish the section with a screenshot of the “m” with wavy green underline after the WebKit patch.

    Green wavy underline covering the full length of the letter m (WebKit) Green wavy underline covering the full length of the letter “m” (WebKit)

    Round trip

    As mentioned in the previous section, thanks to porting the patch to WebKit and working on a WPT test we found out a mistake on the first Chromium fix.

    So we’re back in Chromium where we were clipping the wavy text decoration with the wrong offset, so it looks like it was a little bit shifted to the left (specially when using big fonts). I’m repeating here the image for the initial Chromium fix, adding a grey background so it’s easier to notice the problem and compare with the final fix. There you can see that the wavy underline starts more on the left than expected, and ends earlier than the “m” letter.

    Green wavy underline covering the full length of the letter m (Chromium). Initial fix. Green wavy underline covering the full length of the letter “m” (Chromium). Initial fix

    The patch to fix that was pretty simple, so we landed it in Chromium 98.0.4697. And this is the final output with the text decoration positioned in the proper place.

    Green wavy underline covering the full length of the letter m (Chromium). Final fix. Green wavy underline covering the full length of the letter “m” (Chromium). Final fix

    Other issues

    In addition to an improved rendering on static wavy text decorations, these fixes have some nice effects when animations are involved. See the following video showing an example with animations (stealing a letter-spacing example from Delan’s latest blog post), on the left you can see Chromium (buggy version on top, fixed one on bottom) and on the right WebKit (again buggy on top and fixed on bottom).

    Video showing the fix (top left: Chromium buggy, bottom left: Chromium fixed, top right: WebKit buggy, bottom right: WebKit fixed)

    But as usual there’s still something else, in this case very related to this topic. There’s the concept of decorating box in CSS Text Decoration spec, and that hasn’t been implemented in Chromium and WebKit yet (though Firefox seems to be doing that right in most cases, except for dotted text decorations).

    This issue is quite noticeable when you have different elements in the same line (like a <strong> element), or different font sizes in the same line. See the next video that shows this problem in Chromium (on the left) and Firefox (on the right, where only dotted text decorations have problems).

    element. The video shows the behavior in Chromium (buggy) and Firefox (working fine except for dotted)."> element. The video shows the behavior in Chromium (buggy) and Firefox (working fine except for dotted)." class="center-block" style="width: 100%;" controls="" loop="">

    Video showing the problem related to decorating box (left: Chromium, right: Firefox)

    This has been a problem in Chromium and WebKit forever, even native spelling and grammar error markers have the same issue (thought it’s less noticeable as they’re painted always in a small size). Though even when this isn’t a strictly a blocker for all this work, this is something we’re looking forward to get fixed too.


    The main takeaway from this blog post is how browser interoperability plays a key important role in the implementation of web platform features. Which is something we’re very concerned of at Igalia.

    The fact that we fixed this issue in Chromium and WebKit at the same time helped to get more eyes looking into the same code, which is usually very beneficial.

    We ended up not just fixing the issue in both implementations (Chromium and WebKit), but also adding new WPT tests that would be useful for any other implementation and to prevent regressions in the future. Even as a positive side effect, WebKit added support for mismatch reference tests as part of this work.

    Finally, thanks to all the people that helped to make this happen by providing ideas, feedback and reviewing the patches; particularly Delan Azabani (Igalia), Myles Maxfield (Apple) and Stephen Chenney (Google).

    December 28, 2021 11:00 PM

    December 20, 2021

    Release Notes for Safari Technology Preview 137

    Surfin’ Safari

    Safari Technology Preview Release 137 is now available for download for macOS Big Sur and macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 285788-286534.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Elements Tab
      • Enhanced autocomplete to support mid-line completions (r285851)
      • Styles
        • Added a inline swatch for CSS align-content (r285983)
    • Layers Tab
      • Fixed the position of composited layers with a box-shadow (r285839)
    • Console Prompt
      • Fixed console script evaluation not working or being performed in an unexpected execution context after refresh or navigation (r286412)
    • Web Inspector Interface
      • Enabled left docking when in left-to-right and right docking when in right-to-left mode (r285974)


    • Enabled support for :has() pseudo-class by default (r286495, r286135, r286302, r286180, r286226, r286494, r286433, r286188, r286169, r286365)
    • Added support for new srgb-linear, xyz-d50 and xyz-d65 colorspaces (r286168)
    • Added support for oklab() and oklch() colors (r286191)
    • Added support for replaced elements with intrinsic ratio and no intrinsic size (r286206)
    • Added support for *vi (inline) and *vb (block) viewport units (r286458)
    • Added helper to add CSS property with implicit default (r285837)
    • Changed to not shrink tables bellow their intrinsic sizes (r286207)
    • Changed SVG images used as grid items to use the overriding logical width and height when defined to compute the logical dimensions (r286100)
    • Changed dynamic dv* viewport units to ignore the page scale (r286350)
    • Fixed :hover with descendant selector invalidating correctly in shadow trees (r286063)
    • Fixed rem in media queries to be calculated using font-size: initial, not root element font-size (r286123)
    • Fixed sticky th or td in table to stop at the specified top (r286417)
    • Implemented parsing and animation support for ray() shape accepted by offset-path (r286086)
    • Transferred size for grid item with an aspect-ratio and stretch alignment against the definite row (r285987)
    • Updated color-mix() to the latest spec (r286196)


    • Added Intl.NumberFormat.formatRangeToParts for ICU 69~ platforms (r286255)
    • Implemented Date.prototype.toTemporalInstant() (r286149)
    • Revised JSON.parse atomize policy for performance and compatibility with the other engines (r285955)
    • Accelerated public class field initialization (r286251)


    • Fixed WebAssembly memory.fill out of bounds error message (r286092)

    Experimental Model Element

    • Added support for mouse-based manipulation of <model> on macOS (r285986)
    • Added audio support (r286065)
    • Added support for controlling looping animations (r286066)
    • Added support for getting and setting the camera (r286019)
    • Added support for pausing and resuming animations (r286048)
    • Added support for seeking animations (r286068)


    • Added accessibility attributes for <model> (r286406)

    Web API

    • Added initial implementation for the Web Lock API (r286284)
    • Fixed Cross-Origin-Embedder-Policy: require-corp to not prevent loading of data-URL images (r285823)
    • Fixed empty <input type=file> controls not showing up in the urlencoded and text/plain enctypes (r285808)
    • Fixed empty <input type=file> represented incorrectly in FormData (r285861)
    • Fixed modal dialogs to make the root element unfocusable (r285791)
    • Fixed validity.valueMissing to not rely on the element’s disabled state for inputs of type radio, file, or checkbox (r286413)
    • Fixed file inputs in non-multipart form submissions showing up as string values in the formdata event (r286427)
    • Implemented FileSystemFileHandle.getFile() (r285912)

    Content Security Policy

    • Fixed missing lineNumber and columnNumber in inline violation reports (r285800)
    • Implemented submitting samples in violation reports (r286150)
    • Fixed CSP DOM reporting which always used the document as the target (r286136)


    • Added support for more requestVideoFrameCallback metadata (r285984)
    • Fixed audio rate issues in WebRTC audio rendering when switching audio output (r285985)
    • Fixed video encoding and decoding for h.264 (r285928)

    Apple Pay

    • Changed PaymentRequest to validate payment method data on construction (r286452)
      • This can (and should) now be used instead of ApplePaySession.supportsVersion.

    Web Extensions

    • Added support for special matching characters (*, |, ||, and ^) in urlFilter of declarativeNetRequest rules instead of treating them as regex patterns
    • Added permission prompting inside Web Inspector for devtools extension tabs
    • Added support for CSS injections and removals of more than one file with browser.scripting

    December 20, 2021 09:23 PM

    December 14, 2021

    Wide Gamut 2D Graphics using HTML Canvas

    Surfin’ Safari

    Most colors used on the Web today are sRGB colors. These are the colors that you specify with the familiar #rrggbb and rgb(r, g, b) CSS syntax, and whose individual color components are given as values in the range [0, 255]. For example, rgb(255, 0, 0) is the most saturated, pure red in the sRGB color space. But the range of colors in sRGB — its color gamut — does not encompass all colors that can be perceived by the human visual system, and there are displays that can produce a broader range of colors.

    sRGB is based on the color capabilities of computer monitors that existed at the time of its standardization, in the late 1990s. Since then, other, wider gamut color spaces have been defined for use in digital content, and which cover more of the colors that humans can perceive. One such color space is Display P3, which contains colors with significantly higher saturation than sRGB.

    This browser reports that the display does not support Display P3 colors; figures in this post may not appear as intended.
    A conic gradient showing a range of sRGB colors A conic gradient showing a range of Display P3 colors
    Conic gradients showing fully saturated sRGB (left) and Display P3 (right) colors. Viewed in a browser and on a display supporting Display P3, the colors in the circle on the right will show as more intense than those on the left. (View as standalone page.)
    For a more in depth introduction to color spaces, see Dean Jackson’s earlier post, Improving Color on the Web.

    Today, there are many computer and mobile devices on the market with displays that can reproduce all the colors of the Display P3 gamut, and the Web platform has been evolving over the last few years to allow authors to make best use of these displays. WebKit has supported wide color images and video since 2016, and last year became the first browser engine to implement the new color syntax defined in CSS Color Module Level 4 where colors can be specified in a given color space (like color(display-p3 1 0 0), a fully saturated Display P3 red).

    One notable omission in wide gamut color support, until now, has been in the HTML canvas element. The 2D canvas API was introduced before wide gamut displays were common, and until now has only handled drawing and manipulating sRGB pixel values. Earlier this year, a proposal for creating canvas contexts using other color spaces was added to the HTML standard, and we’ve recently added support for this to WebKit.

    Drawing on a wide gamut canvas rendering context

    The getContext method on a canvas element, which is used to create a rendering context object with 2D drawing APIs, accepts a new option to set the canvas backing store’s color space.

    <canvas id="canvas" width="400" height="300"></canvas>
    let canvas = document.getElementById("canvas");
    let context = canvas.getContext("2d", { colorSpace: "display-p3" });
    // ... draw on context ...

    The default color space remains sRGB, rather than having the browser automatically use the wider color space, to avoid the performance overhead of color space conversions with existing content. The two explicit color spaces that can be requested are "srgb" and "display-p3".

    Fill and stroke styles can be specified using any supported CSS color syntax.

    let position = 0;
    for (let green of [1, 0]) {
        for (let blue of [1, 0]) {
            for (let red of [1, 0]) {
                context.fillStyle = `color(display-p3 ${red} ${green} ${blue})`;
                context.fillRect(position, position, 40, 40);
                position += 20;
    Colored squares that have been clamped to sRGB Colored squares using Display P3 colors that are outside the sRGB gamut
    Display P3 colors used as fill styles on an sRGB (left) and Display P3 (right) canvas. Colors on the left are clamped to remain within the sRGB gamut. (View as standalone page.)

    Any drawing that uses a color outside the color space of the canvas will be clamped so that it is in gamut. For example, filling a rectangle with color(display-p3 1 0 0) on an sRGB canvas will end up using a fully saturated sRGB red. Similarly, drawing on a Display P3 canvas with color(rec2020 0.9 0 0.9), an almost full magenta in the Rec.2020 color space, will result in pixels of approximately color(display-p3 1.0 0 0.923) being used, since that is the closest in the Display P3 color gamut.

    const COLORS = ["#0f0", "color(display-p3 0 1 0)"];
    for (let y = 20; y < 180; y += 20) {
        context.fillStyle = COLORS[(y / 20) % 2];
        context.fillRect(20, y, 160, 20);
    A filled square of full sRGB green Stripes of full sRGB green and full Display P3 green
    Stripes of interleaved Display P3 and sRGB colors on an sRGB (left) and Display P3 (right) canvas. Because colors are clamped to remain within the gamut of the canvas, the two shades of green are indistinguishable on the sRGB canvas. (View as standalone page.)
    On macOS, you can use the ColorSync Utility to convert color values between sRGB, Display P3, Rec.2020, and some other predefined color spaces.

    Wide gamut colors are usable in all canvas drawing primitives:

    • as the fill and stroke of rectangles, paths, and text
    • in gradient stops
    • as a shadow color

    Pixel manipulation in sRGB and Display P3

    getImageData and putImageData can be used to get and set pixel values on a wide gamut canvas. By default, getImageData will return an ImageData object with pixel values in the color space of the canvas, but it is possible to specify an explicit color space that does not match the canvas, and a conversion will be performed.

    let context = canvas.getContext("2d", { colorSpace: "display-p3" });
    context.fillStyle = "color(display-p3 0.5 0 0)";
    context.fillRect(0, 0, 100, 100);
    let imageData;
    // Get ImageData in the canvas color space (Display P3).
    imageData = context.getImageData(0, 0, 1, 1);
    console.log(imageData.colorSpace);  // "display-p3"
    console.log([]);   // [128, 0, 0, 255]
    // Get ImageData in Display P3 explicitly.
    imageData = context.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
    console.log(imageData.colorSpace);  // "display-p3"
    console.log([]);   // [128, 0, 0, 255]
    // Get ImageData converted to sRGB.
    imageData = context.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
    console.log(imageData.colorSpace);  // "srgb"
    console.log([]);   // [141, 0, 0, 255]

    The ImageData constructor similarly takes an optional options object with a colorSpace key.

    let context = canvas.getContext("2d", { colorSpace: "display-p3" });
    // Create and fill an ImageData with full Display P3 yellow.
    let imageData = new ImageData(10, 10, { colorSpace: "display-p3" });
    for (let i = 0; i < 10 * 10 * 4; ++i)[i] = [255, 255, 0, 255][i % 4];
    context.putImageData(imageData, 0, 0);

    As when drawing shapes using colors of a different color space, any mismatch between the ImageData and the target canvas color space will cause putImageData to perform a conversion and potentially clamp the resulting pixels.

    Serializing canvas content

    The toDataURL and toBlob methods on a canvas DOM element produce a raster image with the canvas contents. In WebKit, these methods now embed an appropriate color profile in the generated PNG or JPEG when called on a Display P3 canvas, ensuring that the full range of color is preserved.

    Drawing wide gamut images

    Like putImageData, the drawImage method will perform any color space conversion needed when drawing an image whose color space differs from that of the canvas. Any color profile used by a raster image referenced by an img, and any color space information in a video referenced by a video (be it a video file or a WebRTC stream), will be honored when drawn to a canvas. This ensures that when drawing into a canvas whose color space matches the display’s (be that Display P3 or sRGB), the source image/video and the canvas pixels will look the same.

    Here is an interactive demonstration of using canvas to make a sliding tile puzzle. The tiles are drawn by applying a clip path and calling drawImage pointing to the img element on the left, which references a wide gamut JPEG. Toggling the checkbox shows how the colors are muted when an sRGB canvas is used.

    Sliding tile puzzle. Toggling the checkbox will change whether an sRGB or a Display P3 canvas is used. (View as standalone page.)

    Web Inspector support

    Web Inspector also now shows color space information for canvases to help ensure your canvases’ backing stores are in the expected color space.

    In the Graphics tab, the Canvases Overview will display the color space for each canvas next to the context type (e.g. 2D) on each canvas overview tile.

    After clicking on a Canvas overview tile to inspect it, the color space is shown in the Details Sidebar in the Attributes section.

    Browser support

    Wide gamut canvas is supported in the macOS and iOS ports of WebKit as of r283541, and is available in Safari on:

    • macOS Monterey 12.1 and above
    • iOS 15.1 and above

    Safari is the first browser to support drawing shapes, text, gradients, and shadows with wide gamut CSS colors on Display P3 canvases. All other features, including getImageData, putImageData, and drawImage on Display P3 canvases, are supported in Safari and in Chrome 94 and above.

    Feature detection

    There are a few techniques you can use to detect whether wide gamut display and canvas support is available.

    Display support: To check whether the display supports Display P3 colors, use the color-gamut media query.

    function displaySupportsP3Color() {
        return matchMedia("(color-gamut: p3)").matches;

    Canvas color space support: To check whether the browser supports wide gamut canvases, try creating one and checking the resulting color space.

    function canvasSupportsDisplayP3() {
        let canvas = document.createElement("canvas");
        try {
            // Safari throws a TypeError if the colorSpace option is supported, but
            // the system requirements (minimum macOS or iOS version) for Display P3
            // support are not met.
            let context = canvas.getContext("2d", { colorSpace: "display-p3" });
            return context.getContextAttributes().colorSpace == "display-p3";
        } catch {
        return false;

    CSS Color Module Level 4 syntax support: To check whether the browser supports specifying wide gamut colors on canvas, try setting one and checking it wasn’t ignored.

    function canvasSupportsWideGamutCSSColors() {
        let context = document.createElement("canvas").getContext("2d");
        let initialFillStyle = context.fillStyle;
        context.fillStyle = "color(display-p3 0 1 0)";
        return context.fillStyle != initialFillStyle;

    Future work

    There are a few areas where wide gamut canvas support could be improved.

    • 2D canvas still exposes image data as 8 bit RGBA values through ImageData objects. It may be useful to support other pixel formats for a greater color depth, such as 16 bit integers, or single precision or half precision floating point values, especially when wider color gamuts are used, since increased precision can help avoid banding artifacts. This has been proposed in an HTML Standard issue.
    • The two predefined color spaces that are supported are sRGB and Display P3, but as High Dynamic Range videos and displays that support HDR become more common, it’s worth consdering allowing 2D canvas to use these and other color spaces too. See this presentation at the W3C Workshop on Wide Color Gamut and High Dynamic Range for the Web from earlier this year, which talks about proposed new color space and HDR support.
    • Canvas can be used with context types other than 2D, such as WebGL and WebGPU. A proposal for wide gamut and HDR support in these contexts was presented at that same workshop.

    In summary

    WebKit now has support for creating 2D canvas contexts using the Display P3 color space, allowing authors to make best use of the displays that are becoming increasingly common. This feature is enabled in Safari on macOS Monterey 12.1 and iOS 15.1.

    If you have any comments or questions about the feature, please feel free to send me a message at @heycam, and more general comments can be sent to the @webkit Twitter account.

    Further reading

  • W3C Workshop on Wide Color Gamut and High Dynamic Range for the Web (W3C)
  • December 14, 2021 05:00 PM

    December 13, 2021

    New WebKit Features in Safari 15.2

    Surfin’ Safari

    The internet has always been about communication and collaboration. It started with asynchronous messages made of text. As it matured, the internet became real-time. Then the web came along, adding images, and later, video. Websites provided a means to publish, to broadcast, to run stores, to gather communities and create worlds.

    Now, the web is maturing to the point where web apps make rich collaboration experiences possible — including digital creation. Recent updates to WebKit bring a number of improvements to Safari 15.2 that focus on supporting creative applications and leveraging the incredible power of today’s hardware.

    WebAssembly Enhancements

    Web Assembly (Wasm) is a low-level assembly language that allows a multitude of programming languages like C/C++, C#, Objective-C, Swift, Python, Java or even Cobol to be compiled to run on the web at near native speed — without the user needing to install anything special. It’s designed to work alongside of JavaScript, allowing sites to use both together. Wasm provides the tools needed to bring powerful software applications to the web.

    In Safari 15.2, the addressable memory for Wasm has been expanded to 4GB, opening up possibilities for bigger and more powerful applications. The addition of zero-cost exception handling also provides potential performance gains.

    COOP/COEP HTTP Headers

    Shared memory provides powerful functionality for native applications, but on the web, such power must be balanced with strong security protections. SharedArrayBuffer was supported in WebKit for Safari 10.1–11, but was disabled along with other browsers due to the risk of using it for speculative execution attacks like Spectre.

    Safari 15.2 adds support for Cross-Origin-Opener-Policy (COOP) and Cross-Origin-Embedder-Policy (COEP) HTTP response headers. Sites can adopt these headers to opt into process isolation and be better protected. If sites serve both Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp, they are now able to use SharedArrayBuffer and Wasm threading again.

    Wide gamut support for Canvas

    These days modern creative tools depend on amazing camera and gorgeous displays. Yet, most colors on the Web today are sRGB colors, which match the limited color capabilities of computer monitors from the late 1990s. The human visual system can perceive a much broader range of colors. Today’s modern displays reproduce the colors of the Display P3 gamut, with significantly higher saturation than sRGB.

    Since 2016, WebKit has supported wide color images and video, and last year became the first browser engine to implement the new color syntax defined in CSS Color Module Level 4. One notable omission in wide gamut color support was in the HTML canvas element. Earlier this year, a proposal for support was added to the HTML standard, and now, in Safari 15.2, WebKit adds wide gamut support — including Display P3 — for use in canvas.

    Read more about the details, with demos of stunning results, in Wide Gamut 2D Graphics using HTML Canvas.

    More information

    For more on what’s in Safari 15.2, including bug fixes, see the Safari 15.2 Release Notes.


    Safari 15.2 is available on macOS Monterey, macOS Big Sur and macOS Catalina. To update on macOS, go to Apple menu  > System Preferences, and click Software Update.

    Safari 15.2 is available on iOS and iPadOS 15.2. To update, go to Settings > General, then tap Software Update.


    If you run into issues, we welcome your bug reports for Safari, or WebKit bugs for web content issues. Send us a tweet @webkit to share your thoughts on this release.

    December 13, 2021 07:13 PM

    December 08, 2021

    Release Notes for Safari Technology Preview 136

    Surfin’ Safari

    Safari Technology Preview Release 136 is now available for download for macOS Big Sur and macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 285101-285788.

    Note: Tab Groups do not sync in this release.


    • Added support for contain: paint (r285583)
    • Added support for the revert-layer value (r285624)
    • Added flex-basis: content support (r285709)
    • Fixed ::slotted element style to invalidate correctly in nested case (r285211)
    • Fixed ::slotted to not match an active <slot> (r285209)
    • Fixed :host::part(foo) selector to select elements inside shadow roots (r285262)
    • Fixed a mask or isolation to set transform-style to flat (r285482)
    • Fixed border-radius inline style to serialize with valid syntax (r285235)
    • Fixed font-synthesis inline and computed style to be in canonical order (r285383, r285384)
    • Fixed rendering bug with height: min-content, position: absolute, and box-sizing: border-box (r285495)
    • Fixed the default computed value for content to be none for ::before and ::after (r285621)
    • Implemented parsing and animation support for offset-path (r285343)
    • Implemented Scroll To Text Fragment directive parsing (r285528)
    • Implemented transform: perspective(none) (r285255)
    • Removed non-standard -webkit-border-fit CSS property (r285615)
    • Updated the content-size grid width before laying out a grid item with block constraints and aspect-ratio (r285497)

    GPU Process

    • Fixed enabling “media source inline painting” by default to work when using GPU Process for media (r285410)


    • Implemented IntlNumberFormat v3 (formatRangeToParts is not implemented yet) (r285418)
    • Implemented Temporal.Instant (r285178)

    Web API

    • Implemented custom element definition’s disable shadow flag (r285740)
    • Fixed the cssText property for a computed style to return an empty string (r285604)


    • Fixed showing languages and subtitles tracks button and menu for <audio> (r285216)

    Web Animations

    • Added support for composite operations for software animations (r285397)
    • Fixed accelerated animations with a single keyframe not accounting for prior forward-filling animations (r285728)
    • Fixed discrete animation of content property not working (r285423)
    • Improved additivity support when animating the transform property (r285631)


    • Implemented add_virtual_authenticator and remove_virtual_authenticator for WebDriver (r285267)

    Private Click Measurement

    • Fixed occasionally dropped attribution reports for Private Click Measurement (r285170)

    Web Extensions

    • Added support for manifest_version 3 and service_worker background scripts, while also supporting the option of using non-persistent background pages
    • Added support for script and style injection via the browser.scripting APIs
    • Added support for dynamic and session rules via the browser.declarativeNetRequest APIs
    • Fixed an issue with new tab pages not being remembered when switching from Favorites
    • Fixed an issue with long extension descriptions causing the title to be cut-off in Preferences
    • Enforces limits on the size and number of items in extension sync storage

    Other Bugs

    • Fixed opening local HTML files when the Develop menu was enabled (r285130)
    • Stopped using a timer to dispatch the source element’s error event asynchronously (r285413)

    December 08, 2021 11:03 PM

    November 16, 2021

    PCM for In-App Direct Response Advertising

    Surfin’ Safari

    Private Click Measurement (PCM) can now be used for in-app direct response advertising using SFSafariViewController. Try it out in our iOS 15.2 beta.

    What is PCM?

    PCM is a proposed web standard for measuring the effectiveness of click-through advertising in a privacy-preserving way. It allows for 8 bits of data on the click source site to be combined with 4 bits of data on the click destination site to measure which advertising is driving sales. The combined 8+4 bits of data is sent to both the click source and destination in an attribution report that doesn’t carry any user or device identifiers. The net result is a report that says “Someone who clicked ad X on website A later converted with value Y on website B.”

    PCM shipped in iOS/iPadOS 14.5 and in Safari 14.1 on macOS. Its privacy-preserving nature means it can be used without getting the user’s permission to track according to AppTrackingTransparency.

    What is SFSafariViewController?

    SFSafariViewController is a ready-built in-app web browser on iOS/iPadOS with full-fledged Safari features such as Reader, AutoFill, Fraudulent Website Detection, content blocking, and bookmarks. User activity and interaction with SFSafariViewController are not visible to your app which means users can safely browse the web in it and you do not need to secure data between your app and SFSafariViewController.

    On Direct Response Advertising

    Our introductory blog post on PCM featured two important FAQ entries on app-to-web advertising – on the subject of taking the user to the device’s browser and on the subject of an in-app experience.

    When to Take the User To the Device’s Browser

    PCM app-to-web in iOS 14.5 had to take the user to the device’s browser. This was designed to support re-engagement. Stored clicks in PCM are valid for 7 days and customers who are ready to take action only after a few hours or days, will most likely go find the merchant website in their browser. They’ll either look up the tab where they left off, use a bookmark they might have saved, use their search provider to find the right webpage, or enter the website’s address directly in the URL bar.

    For the stored click data to be readily available when the user re-engages in this fashion, the initial click needs to take the user to their browser. This is still true going forward.

    When to Handle the Tap In-App

    Another form of click-through advertising is called direct response. In such cases, the user is not expected to think about converting for an extended period of time, but rather take action directly on the webpage they land on. It could be a limited offer or a product priced so that the user doesn’t feel like they have to think it over.

    Developers and advertisers have told us they want to be able to provide more of an in-app experience for direct response advertising, rather than take the user to the device’s browser. They want a seamless in-app experience for users who tap on an ad, check out the product page, decide to buy or not, and then want to go back to the hosting app.

    Our intro blog post covered this request in the FAQ section, and we said “We are interested in this but don’t have a solution yet.” Today we have a solution. PCM is now capable of supporting in-app advertising with new API for SFSafariViewController.

    PCM App-to-Web with SFSafariViewController

    SFSafariViewController provides a great in-app browsing experience where the user can store Safari bookmarks that can sync across devices, has access to autofill of credentials and payment card info, and Apple Pay. It is simply a great place to take the user as part of direct response advertising.

    Ephemeral Clicks to Prevent Click Fraud

    PCM will only store click data and schedule an attribution report if the user triggers a conversion in the SFSafariViewController that they opened through the click. A tap which navigates to a website in SFSafariViewController without a matching triggering event will not be stored. As a result, an instance of SFSafariViewController can only hold one non-converted click at a time, whereas all converted clicks will be stored and result in reports.

    This ensures that a hosting app cannot speculatively store clicks in its SFSafariViewController for fraudulent reasons. It also ensures that this use of PCM really is geared toward direct response advertising.

    Attributions are Per Hosting App

    Taps in different apps navigating the user to the same advertised website do not affect each other. Every app gets its own attribution. Again, this feature is for direct response advertising so a customer who buys a product twice based on ad clicks in two different apps will generate two attribution reports.

    Attribution Reports Don’t Require Your App to Run

    One particular challenge for the kind of delayed attribution reporting PCM uses is what to do if the user doesn’t use the hosting app frequently or doesn’t use it around the time when the report is supposed to be sent out. We have made sure that pending attribution reports from PCM app-to-web with SFSafariViewController are sent independent of if the hosting app where the click happened is running or not.

    The API

    As shown in our introductory blog post on PCM, apps can already use Private Click Measurement with Safari by putting a UIEventAttribution on a UISceneOpenExternalURLOptions and using it with UIScene’s openURL:options:completionHandler:.

    In iOS 15.2 beta, a new attribute of type UIEventAttribution is added to the existing class SFSafariViewControllerConfiguration:

    @available(iOS 15.2, *)
      @NSCopying var eventAttribution: UIEventAttribution?

    You can optionally use it when opening a URL in SFSafariViewController, like this:

    func didTapOnAdWithIdentifier(advertisementIdentifier: UInt8, url: URL) {
        let attribution = UIEventAttribution(
            sourceIdentifier: advertisementIdentifier, 
            destinationURL: url, 
            sourceDescription: "Ad for toy XYZ.",
            purchaser: "Toy Example Company")
        let configuration = SFSafariViewController.Configuration()
        configuration.eventAttribution = attribution
        present(SFSafariViewController(url: url, configuration: configuration), animated: false)

    When an UIEventAttribution is part of the configuration, SafariViewService checks that a tap on an UIEventAttributionView preceded the opening of SFSafariViewController to guarantee click-through attribution. Then the information from the UIEventAttribution object is checked before it’s given to WebKit for processing.

    Private Click Measurement works as previously from that point.

    Debugging Your App

    See “Testing and Debugging” in our introductory blog post on PCM for how to turn on PCM Debug Mode.

    Make sure to restart your app after enabling PCM Debug Mode with SFSafariViewController. If you are having trouble getting PCM Debug Mode to turn on or off, try restarting the device.

    Please Provide Feedback

    We really appreciate all the developer and ad tech feedback we’ve received so far on Private Click Measurement. Prioritizing PCM for in-app measurement was the result of such feedback. There are three ways for you to continue to tell us what you think:

    • The standards proposal repository in the W3C Privacy Community Group for anything related to the specified web parts of PCM, i.e. feedback on the proposed standard as it would work in any web engine.
    • for anything specific to the WebKit implementation of PCM.
    • Apple Feedback Assistant for anything specific to the UIKit or SFSafariViewController APIs for PCM.

    November 16, 2021 05:06 PM

    November 15, 2021

    Release Notes for Safari Technology Preview 135

    Surfin’ Safari

    Safari Technology Preview Release 135 is now available for download for macOS Big Sur and macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 284370-285101.

    Note: Tab Groups do not sync in this release.

    Web Inspector and Web Driver

    • Fixed “testForLinkLabels” Accessibility audit to ignore anchors if aria-hidden (r284986)
    • Added support for the acceptInsecureCerts capability (r285164)


    • Media queries affect Cascade Layer order (r284859)
    • Enabled accent-color by default (r284634)
    • Added support for small svw/svh/svmin/svmax, large lvw/lvh/lvmin/lvmax, and dynamic dvw/dvh/dvmin/dvmax viewport units (r284628)
    • Added flex-basis: content support (r284440)
    • Added support for contain: style for counters (r284642, r284755)
    • Added support for ::before and ::after pseudo elements after ::slotted (r284973)
    • Added support for more CSS properties for ::marker (r284519)
    • Allowed :is and :where after all pseudo elements (r285054)
    • Made :-webkit-any() a synonym of :is() (r285032)
    • Fixed ::part(foo):hover (r284865)
    • Serialized :part() argument as identifier (r284863)
    • Fixed :host invalidation when combined with pseudo classes in descendant position (r285100)
    • Prevented clamping flex base size with min-height, max-height, min-width, and max-width (r284397)
    • Fixed sizing of orthogonal elements with percentage margins (r284773)
    • Fixed -webkit-background-clip: text to paint correctly for inline box spanning multiple lines (r284380)
    • Fixed box-shadow and text-shadow to yield float values while interpolating (r284437)
    • Fixed CSS serialization affecting grid-auto-flow (r284876)
    • Fixed percentages on orthogonal replaced children (r284548)
    • Fixed the border-radius value from .style to be readable when it includes a var() (r285015)
    • Fixed opacity to flatten when combined with transform-style: preserve-3d (r285021)

    Web API

    • Enabled lazy image loading by default (r284995)
    • Added support for rel="noopener/noreferrer" on <form> elements (r284749)
    • Exposed MediaCapabilities to Workers (r284443)
    • Fixed anchor.relList.supports("opener") to return true (r284745)
    • Fixed changing the src attribute of the <img> element inside an ImageDocument to trigger a load (r284901)
    • Fixed and friends to use the correct document as a source for reset document’s URL (r284758)
    • Fixed form navigations with target="_blank" to not have an opener (r284821)
    • Fixed form submission to be cancelled if the form gets detached from inside the formdata event handler (r284660)
    • Fixed JavaScript URL result to be treated as UTF-8 bytes (r284934)
    • Fixed Origin of opaque blob: URLs to be null instead of an empty string (r284478)
    • Fixed selection extend() with no ranges to trigger an exception (r285084)
    • Fixed the intrinsic size of a picture image inside a template (r284667)
    • Updated appearance of <datalist> indicator (r284626)


    • Fixed misplaced position: fixed content with async-scrollable iframes when switching tabs (r284738)


    • Ensured CanvasRenderingContext2D.drawImage(video) uses the right color space (r284439)


    • Changed to obtain consent to create a new credential when the platform authenticator is in excludedCredentials. This improves compliance with the WebAuthn spec (Step 3.1 of makeCredential). (r284413)


    • Added support for requestVideoFrameCallback API for MediaStreamTrack-based backends (r284528)
    • Fixed video appearing blank with only audio playing if video element isn’t appended to the DOM tree (r284741)
    • Updated WebM with invalid size to fail to load with error (r284434)


    • Decreased WebRTC latency by pulling data more often (r284860)
    • Changed to fallback to SW decoder in case of VP9-SVC (r284523)
    • Changed to always set the color space for incoming H.264/265 streams (r284433)
    • Ensured synchronized rendering of incoming audio tracks (r285027)
    • Fixed latent audio over peer connections when changing the output (r284674)


    • Fixed an issue where high-performance WebGL wasn’t getting the correct GPU (r284669)

    App Extensions

    • Fixed an issue where App Extension toolbar items would not remember their position or stay removed from the toolbar

    Web Extensions

    • Fixed an issue where entries were saved into the storage area. If unable to locate sync storage entries, check in the local storage area and do a one-time migration to the sync storage area

    November 15, 2021 09:30 PM

    October 27, 2021

    Release Notes for Safari Technology Preview 134

    Surfin’ Safari

    Safari Technology Preview Release 134 is now available for download for macOS Big Sur and macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 282317-284370.

    Note: Tab Groups do not sync in this release.

    Web Inspector

    • Graphics Tab
      • Added showing color space for canvases in the Graphics tab on the overview cards (r283572)
    • Styles Tab
      • Changed to format style declarations after editing (r283723)
    • Other
      • Added context menu support for the tab content view (r283859, r283921)


    • Enabled CSS Cascade Layers by default (r283218)
    • Unprefixed -webkit-appearance and added support for the auto value (r283858, r284098)
    • Added support for the x resolution unit outside of image-set (r282396)
    • Added support for text-decoration-skip-ink (r282397)
    • Changed to consider overflow-wrap: anywhere when calculating min-content intrinsic sizes (r283493)
    • Fixed computed style for transform-origin on SVG boxes (r282379)
    • Fixed overflow to be computed with the actual logical bottom in CSS Grid (r282463)
    • Fixed RTL for an out of flow child in CSS Grid (r282340)
    • Fixed SVG linear gradients getting drawn incorrectly sometimes (r282443)
    • Fixed radial-gradient to accept calc() values that combine length and percent (r283561)
    • Implemented exp, and log functions in calc functions (r282795)
    • Implemented allowing NaN, infinity, and -infinity in calc (r283434)
    • Implemented atan, acos, asin, atan2 in calc functions (r283013)
    • Updated CSS Cascade Layers with recent spec change: unlayered styles are highest priority (r284182)
    • Updated transform-origin to not accept four lengths (r282359)


    • Enabled font palette support:
      • Added parsing support for font-palette-values (r282806)
      • Added parsing support for font-palette (r282851)
      • Added pushing font-palette-values data into CSSFontSelector (r282838)
      • Added support for (r283140)
      • Added Web Inspector support for font-palette (r282987)
      • Allowed base-palette can accept "light" or "dark" (r283398)
      • Changed CSSFontPaletteValuesRule to not be map-like (r283219)
      • Changed to align with CSS Fonts specification changes (r283130)
      • Fixed shadowing of @font-palette-values rules (r283756)
      • Handle CSSOM style mutation of font-palette and font-palette-values (r283032, r283031)
      • Made negative integers in @font-palette-values invalid (r283197, r283540)
      • Made sure empty font families do the right thing for font palettes (r283075)
      • Prevented parsing unimplemented font palette features (r283752)
      • Removed the "none" value from font-palette (r283536)
      • Renamed override-color to override-colors (r283159)
      • Required font-families inside @font-palette-values to be case insensitive (r283794)
      • Required font palette names to start with two hyphens (r283221)
      • Stopped parsing context-sensitive colors in override-color (r283537)
    • Updated the implementation of the CSS Font Loading API to be closer to the spec and other browsers:
    • Fixed CSSFontFaceSrcValue.cssText to be quoted consistently with other browsers (r282442)


    • Enabled support for ScrollOptions’ ScrollBehavior and CSS scroll-behavior properties (r284029)
    • Moved smooth scroll animations to run on the scrolling thread (r283871)
    • Fixed scrollIntoView to not take into account sticky positioning offsets (r283546)
    • Fixed properly redrawing a sticky element inside another sticky element on scroll (r284084)


    • Enabled 2D canvas color space support on Apple platforms (r283541)
    • Updated converting an SVG image for canvas drawImage to choose an appropriate color space (r283531)

    Dialog Element

    • The <dialog> element is now enabled by default (r284155), also including:
      • support for the CSS ::backdrop pseudo element
      • support for the top layer
      • <form method="dialog"> support
    • The inert attribute is available for testing behind an experimental flag with the same name


    • Allowed WASM to use up to 4GB (r284330)
    • Implemented the WebAssembly exception handling proposal (r283852)


    • Enabled unlinked Baseline JIT for performance (r283139)
    • Fixed Intl.DateTimeFormat#resolvedOptions not to return detailed information of formatting if dateStyle or timeStyle is set (r283460)
    • Fixed Intl.supportedValuesOf to populate emoji and eor for collation (r282897)
    • Fixed syntactic production for #x in expr (r282968)
    • Optimized JSON.parse object creation (r282468)
    • Optimized put-by-val with for-in (r283098)
    • Optimized JSON.stringify property enumeration (r282707)
    • Refined RegExp#compile based on regexp-legacy-features proposal (r283874)

    Web API

    • Enabled BroadcastChannel (r282426)
      • Implemented top-origin and frame-origin partitioning for BroadcastChannel (r282366)
    • Enabled Storage API by default (r284273)
    • Enabled FileSystemAccess and AccessHandle by default (r284131)
      • Implemented FileSystemSyncAccessHandle read() and write() (r284059)
    • Implemented the borderBoxSize and contentBoxSize parts of ResizeObserver (r282441)
    • Implemented CSP script-src-elem, style-src-elem, script-src-attr and style-src-attr directives (r284254)
    • Cached Web Audio PannerNode’s azimuth, elevation, and coneGain for performance (r283740)
    • Changed <model> to be draggable, similar to <img> (r283563)
    • Updated the autofocus attribute behavior to match the latest specification (r283935)
    • Updated to preserve color space when creating ImageBuffers for ImageBitmaps (r282696)


    • Fixed image-rendering: crisp-edges for WebGL canvases (r282335)


    • Fixed the progress bar moving when playback stops (r282374)
    • Fixed createImageBitmap using a HLS video as source always returning a black image (r283585)


    • Added support for WebRTC media capabilities (r284085, r284236)
    • Changed MediaCapabilities to enqueue a task to resolve promises (r284236)


    • Exposed the URL attribute of <video> elements (r283799)
    • Made PDFs loaded via <embed> accessible (r282358)
    • Updated role="math" elements to no longer be considered to have presentational children (r284246)

    Private Click Measurement

    • Allowed measurement of links in nested, cross-site iframes (r283593)

    Web Extensions

    • Allowed more directives to be included in the content_security_policy of an extension’s manifest, such as the sandbox directive

    October 27, 2021 08:59 PM

    October 26, 2021

    New WebKit Features in Safari 15

    Surfin’ Safari

    With the release of Safari 15 for macOS Monterey, iPadOS 15, iOS 15, and watchOS, as well as macOS Big Sur and macOS Catalina, WebKit brings significant advancements in privacy and security, improved interoperability, and a host of new features for web developers. Take a look.

    Web Extensions

    This release brings Safari Web Extensions to iOS and iPadOS. Web Extensions use HTML, CSS, and JavaScript to offer powerful browser customizations. Now developers can create them for every device that supports Safari, using APIs, functionality, and permissions that are increasingly standardized across all browsers. Learn how to build Safari Web Extensions and discover how to convert an existing extension by watching Meet Safari Web Extensions on iOS at WWDC21.

    This year’s release also adds support for the Declarative Net Request WebExtensions API to block content on the web. Learn all about the latest WebExtension APIs by watching Explore Safari Web Extension Improvements at WWDC21.


    WebKit now provides support for theme-color in HTML meta tags, and in Web Manifest. By specifying a theme-color, web developers can change the color of the status bar and overscroll area in Safari on iOS 15. Theme-color also changes the Tab Bar and overscroll area background colors in Compact Tab layout for Safari 15 on macOS Monterey and Big Sur and iPadOS 15.

    In the HTML meta tag, developers can specify separate colors for Dark Mode and light appearance with the media attribute.

    <meta name="theme-color" 
          media="(prefers-color-scheme: light)">
    <meta name="theme-color" 
          media="(prefers-color-scheme: dark)">

    Watch “Design for Safari 15” at WWDC21 to learn more about the Compact Tab bar and how to use theme-color.


    demo of aspect ratio property

    WebKit now supports CSS aspect-ratio. This property can be used to set a preferred aspect ratio on any element, including boxes like divs, iframes for embedded video, or graphic design elements on a page.

    WebKit provides support for the new lab(), lch(), hwb() color syntaxes from Color level 4, providing web developers with ways to express a richer range of colors in Lab, Lch, and Hue-Whiteness-Blackness. WebKit also supports predefined color spaces using the color() function syntax: srgb, display-p3, a98-rgb, prophoto-rgb, rec2020, xyz.

    WebKit supports 12 new values for list-style-type: disclosure-closed, disclosure-open, ethiopic-numeric, japanese-formal, japanese-informal, korean-hangul-formal, korean-hanja-formal, korean-hanja-informal, simp-chinese-formal, simp-chinese-informal, trad-chinese-formal, and trad-chinese-informal.

    1. Apollo
    2. Hubble
    3. Chandra
    4. Cassini-Huygens
    5. Spitzer
    Disclosure closed
    1. Apollo
    2. Hubble
    3. Chandra
    4. Cassini-Huygens
    5. Spitzer
    Ethiopic Numeric
    1. Apollo
    2. Hubble
    3. Chandra
    4. Cassini-Huygens
    5. Spitzer
    Simplified Chinese informal

    There’s also improved implementation of existing values for list-style-type: armenian, cjk-ideographic, hebrew, lower-armenian, lower-roman, upper-armenian, and upper-roman. See a demo of all of these options at MDN. We also updated WebKit’s implementation of list-style-position:inside to match the updated CSS specification, creating interoperability after a 22 year old debate.

    Watch “Design for Safari 15” at WWDC21 to learn more about the latest updates to CSS.

    Web Inspector

    screenshot of Safari 15's CSS Grid Inspector

    Web Inspector in Safari 15 includes a CSS Grid Inspector overlay for inspecting grid containers on your pages. Watch “Discover Web Inspector Improvements” at WWDC21 to learn more.

    JavaScript and WebAssembly

    This release of WebKit adds support for ES6 Modules in Workers and ServiceWorkers. ES6 Modules provides a powerful way for developers to organize large applications using purpose-specific libraries. Workers/Service Workers provides a way to offload work from the main thread, and are often used for complex applications. Now, developers can use them together — moving work off the main thread, improving performance, while retaining the organizational benefits of modules.

    Additional new capabilities to the JavaScript engine, include:

    • support for top-level await
    • Error.cause
    • private class methods and accessors
    • BigInt64Array and BigUint64Array

    Improvements to WebAssembly include streaming compilation, bulk memory operations, reference types, and non-trapping conversions from float to int.

    You can learn more about the latest JavaScript and WebAssembly updates to WebKit and Safari 15 by watching “Develop Advanced Web Content” at WWDC21.

    Web APIs

    gorgeous weird 3D environments created to show off what's possible

    WebKit now supports WebGL2 (demos). In addition, the WebGL implementation now runs on top of Metal for better performance.

    Web Share level 2 enhancements to Web Share enable sharing files from a web page to an app. See Web Share API for more information.

    User gestures now propagate through requestAnimationFrame with a one-second time limit.

    And now, with Safari 15.1, performance.timeOrigin is available in Web Workers.

    You can learn more about the latest Web APIs in WebKit by watching “Develop Advanced Web Content” at WWDC21.


    Safari 15 includes several media improvements for users and developers. For example, built-in media controls now have Playback Speed and Chapters menus. Plus, the language/subtitle tracks menu is now available on iOS and iPadOS.

    There’s also new support for the Opus audio codec in WebM containers. And on on all iPads that support iPadOS 15, VP9 and WebM in Media Source Extensions (MSE) are now hardware-accelerated.

    Safari 15 also adds support for the MediaSession API to enable SharePlay experiences. You can learn more about creating SharePlay experiences by watching “Coordinate media playback in Safari with Group Activities” at WWDC21.

    Security and Privacy

    Continuing our dedication to privacy and security, Safari on iOS 15 and macOS Monterey supports automatic HTTPS upgrades and hides your IP address from known trackers. Automatic HTTPS upgrades are also supported in Safari 15 on older macOS versions.

    Earlier this year, Safari was the first browser to ship a proposed web standard for measuring advertising in a privacy-preserving way – Private Click Measurement, or PCM. Safari 15 provides three major updates to PCM:

    • Attribution reports also sent to click destination.
    • Click fraud prevention with unlinkable tokens.
    • IP address protection for attribution reports.

    To learn more, read PCM: Click Fraud Prevention and Attribution Sent to Advertiser or watch “Meet privacy-preserving ad attribution” at WWDC21.

    Authentication and Passwords

    WebKit now includes support for on-device verification codes in your app or website for a more secure sign-in experience with iCloud Keychain Password Manager. To use verification codes with Safari and Autofill:

    • Use autocomplete=one-time-code to make an <input> eligible for AutoFill.
    • Use a standard otpauth URL and replace the scheme with apple-otpauth to link directly to the password manager for setup.
    • Use a raster image to enable contextual menus on otpauth QR codes that offer to set up a verification code generator.

    Learn how to support the process in your apps and websites by watching “Secure login with iCloud Keychain verification codes” at WWDC21.

    a diagram of how private keys are routed with WebAuthn

    Despite their prevalence, passwords inherently come with challenges that make them poorly suited to securing someone’s online accounts. Passkeys are WebAuth credentials intended to replace passwords for websites and apps with device sync and backup. The technology is now available in WebKit as a preview. To enable in Safari, choose Develop > Enable Syncing Platform Authenticator. Learn more watching “Move beyond passwords” at WWDC21.


    Apple Pay enhancements allow developers using the Payment Request API to indicate an estimated arrival date for shipping methods, support a coupon code, and mark the shipping method as in-store pickup.


    These improvements are available to users running Safari on iPadOS 15, iOS 15, or Safari 15 on macOS Monterey, macOS Big Sur, or macOS Catalina. These features were also available to web developers in Safari Technology Preview releases. Changes in this release of Safari were included in the following Safari Technology Preview releases: 123, 124, 125, 126, 127, 128, 129.

    Download the latest Safari Technology Preview release to stay at the forefront of future web platform and Web Inspector features. You can also use the WebKit Feature Status page to watch for changes to web platform features you’re interested in.


    If you run into any issues, we welcome your bug reports for Safari or WebKit bugs for web content issues. Send us a tweet @webkit to share your thoughts on this release.

    October 26, 2021 09:27 PM

    September 30, 2021

    Release Notes for Safari Technology Preview 133

    Surfin’ Safari

    Safari Technology Preview Release 133 is now available for download for macOS Big Sur and betas of macOS Monterey. If you already have Safari Technology Preview installed, you can update in the Software Update pane of System Preferences on macOS.

    This release covers WebKit revisions 281797-282317. Note: The changes for these release notes were updated after publishing to account for an incorrect end revision number.

    Note: Tab Groups do not sync in this release.


    • Added support for self-start, self-end, start, end, left, and right values in positional alignment (r282267, r282078, r281840)
    • Added support for percentages in the scale() transform functions, and the scale property (r282144)
    • Added support for sin(), cos(), tan(), e, and pi in calc() (r282162)
    • Fixed incorrect stacking order with an absolutely positioned and negative z-index <div> with a canvas child (r281913)
    • Fixed absolute positioning with orthogonal writing modes (r281995)
    • Fixed right-relative and bottom-relative values in background-position-x and background-position-y (r282234)
    • Fixed incorrect vertical position in table layout when the inline level box has 0px height (r282256)
    • Fixed changing the border size on rows with border-collapse not redrawing (r282266)
    • Fixed position: sticky used within table cells (r282201)
    • Fixed incorrectly calculated position: sticky constraints when the scrolling container has padding and borders (r282138)
    • Fixed an interoperability issue in margin collapsing with overflow: hidden elements (r282085)

    CSS Cascade Layers

    • Added initial support for CSS Cascade Layers in Experimental Features:
      • Added support for computing the order correctly for late added sublayers (r281798)
      • Supported layer argument in @import rules (r281928)

    CSS Font Loading API

    • Updated the implementation of the CSS Font Loading API to be closer to the spec and other browsers:
      • Fixed CSSFontFaceSet.clear() to not clear CSS-connected members (r281842)
      • Updated FontFaceSet methods that need to react to style changes (r282016, r282015, r282015, r282261, r282204)
      • Updated FontFaceSet.add() to throw when called on a CSS-connected font (r281951)


    • Fixed blank braille display in contenteditable elements when the field is followed by another element (r281920)
    • Made PDFs loaded via <embed> accessible (r282358)


    • Enabled Object.hasOwn (r281835)
    • Implemented Temporal.PlainTime behind the flag --useTemporal=1 (r282125)
    • Implemented Temporal.TimeZone behind the flag --useTemporal=1 (r282018)
    • Implemented Temporal.Duration behind the flag --useTemporal=1 (r281838)
    • Implemented self.structuredClone() (r281808)
    • Implemented Object.hasOwn() (r281799)
    • Updated Intl.Locale weekendInfo to list all weekend days instead of range to follow to the latest spec change (r282257)

    Web API

    • Added basic support for Storage API (r282130)
    • Added support for ServiceWorkerGlobalScope.serviceWorker (r281854)
    • Added handling for non-fully active documents in navigator.share() / navigator.canShare() (r282282)
    • Enabled Cross-Origin-Opener-Policy / Cross-Origin-EmbedderPolicy headers support (r282105, r282246)
    • Enabled SharedArrayBuffer support when COOP/COEP headers are used (r281832)
    • Fixed scrollbars on pointer-events: none element still intercepting events (r281991)
    • Implemented top-origin and frame-origin partitioning for BroadcastChannel (r282105)
    • Implemented navigation reporting for Cross-Origin-Opener-Policy (r282305)
    • Implemented getClientRects() for SVG elements (r282316)
    • Updated to always fetch the first manifest if provided (r282026)


    • Added support for RTCError and RTCErrorEvent (r282199)
    • Added support for RTCDataChannel closing event (r282198)
    • Added support for RTCSctpTransport (r282197)
    • Updated timing of RTCPeerConnection descriptions update to align with the WebRTC spec (r282217)
    • Updated signaling state check when applying a local or remote description to align with the WebRTC spec (r281985)
    • Updated getDisplayMedia to capture at the constrained size if possible (r281880)


    • Fixed an incorrect number of frames returned if the decoding frame rate doesn’t match the original in WebM (r282196)
    • Removed Web Audio canPlayType() workaround that made it reports false negatives (r282137)

    Web and App Extensions

    • Added the extension’s icon to extension URL tabs for both app and web extensions
    • Added the extension name as the title of tabs when an app extension page and does not specify a title

    September 30, 2021 05:07 PM