WWDC Quick Look 💓 By SwiftGGTeam
What's new in Safari and WebKit

What's new in Safari and WebKit

Watch original video

Highlight

This is Safari’s annual update roundup, covering all new features from the past seven versions (Safari 15 to 16). Sessions are organized by categories: HTML, CSS, Web Inspector, Web API, JavaScript/WebAssembly, Security and Privacy.


Core Content

Over the past year, Safari and WebKit have released seven versions in a row. The WebKit team said at the beginning that this year has brought a total of 162 Web platform features and improvements (01:18). These updates have focused on answering several questions that developers have long reported: it is difficult to reuse CSS components, it is difficult to select parent states, debugging layouts are not intuitive, and the capability gap between Web Apps and native Apps still exists.

This Session uses a clothing exchange website to tie it all together. The page includes pop-ups for requesting items, carousels for receiving requests, product cards reused in different widths, inventory status synchronized across windows, and drafts that need to be saved offline. Each feature corresponds to a new set of capabilities in Safari 15-16.

HTML: Write less custom pop-up windows and interactive states

In the past, when making a pop-up window, you usually had to handle focus, background mask, keyboard navigation and accessibility yourself. Safari now supports<dialog>and::backdrop, allowing pop-ups and masks to return to the browser’s native capabilities (02:58). If there is a carousel or hidden panel on the page,inertProperties also exclude non-current content from clicks, keyboard navigation, and assistive technology access (03:49).

Image loading also has direct HTML writing. For product images outside the fold, addloading="lazy", the browser will load them when the user scrolls near the image (04:20). This type of capability is suitable for replacing the most common custom code on your site first.

CSS: Components work according to containers

The focus of the CSS section is architecture. The WebKit team made it clear that the top developer request for a new web technology was Container Queries, announcing that it would be released with Safari 16 (05:10). Product cards can be arranged vertically in the sidebar, horizontally in the main area, or in two columns at medium width. The basis for judgment comes from the container in which the component is located, not only from the entire viewport.

@layerSolve another type of long-term problem: In large projects, design systems, tool classes, and business coverage styles are mixed together, and selector priorities will become increasingly difficult to control. Cascade Layers let developers define their own layers, with the order of the entire layer being higher than the specificity of a single selector (07:14).

:has()This allows the parent element to apply styles based on the state of the child element (08:30). When the “Urgent?” checkbox in the form is selected, the entire message form can change color directly, without the need for additional JavaScript to maintain state.

Web API vs JavaScript: Web App is more like a complete application

The Web API part starts with Web Push. Safari 16 supports Web Push in macOS Ventura, with iOS and iPadOS support scheduled to arrive next year (20:22). It is a standards-based implementation, and if the existing Web Push code works in other browsers, it should not need to be modified in Safari (20:41).

Multiple pages from the same source also have a more direct way to collaborate. Broadcast Channel can send the “Item has been received” message in one window to other same-origin windows (22:11). The Origin private file system provides sites with private file storage isolated by origin to save data such as drafts (22:49).

In the JavaScript update, Shared Worker allows multiple pages of the same origin to share a worker, reducing the memory pressure caused by repeatedly starting background tasks for each window (25:08). Arrays have also been addedfindLast()findLastIndex()andat(), making tail lookups and negative index access more straightforward (25:56).

Detailed Content

Native pop-up window:<dialog>and::backdrop

(02:58) The clothing exchange website requires a “Request Item” pop-up window.<dialog>Provides a native overlay container, suitable for hosting request forms.

<!-- <dialog> element -->

<dialog method="dialog">
  <form id="dialogForm">
    <label for="givenName">Given name:</label>
    <input class="focus" type="text" name="givenName">
    <label for="familyName">Family name:</label>
    <input class="focus" type="text" name="familyName">
    <label>
      <input type="checkbox"> Can trade in person
   </label>
   <button>Send</button>
  </form>
</dialog>

Key points:

  • <dialog method="dialog">Declare a native dialog container. -<form id="dialogForm">Place the submission content inside the dialog box.
  • two groups<label>and<input>Collect the name of the requester.
  • checkbox expresses the request condition “whether face-to-face transactions are possible”. -buttonResponsible for submitting forms.

(03:08) The mask behind the pop-up window does not require additional DOM.::backdropDirectly select the background layer of the dialog.

/* ::backdrop pseudo-element */

dialog::backdrop {
  background: linear-gradient(rgba(233, 182, 76, 0.7), rgba(103, 12, 0, 0.6));
  animation: fade-in 0.5s;
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

Key points:

  • dialog::backdropCheck the browser behind the modal to generate the background layer. -backgroundAdd a gradient color to the background layer. -animationBind a fade animation to the background layer. -@keyframes fade-inDefines the change from transparent to opaque.

Interaction disabled:inert

(03:49) Only the current card in the carousel should be interactive.inertDisables clicks, keyboard navigation, and assistive technology access to the element and its children.

// inert attribute

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

Key points:

  • switchToIndex(index)Receives the target carousel item index. -this.items.forEach(...)First set all carousel items to inert. -this.items[index].inert = falseOnly the interactivity of the current item is restored. -this.currentIndex = indexSave the current index for subsequent switching.

Lazy loading of images:loading="lazy"

(04:20) Product listings usually have a lot of pictures. Images outside the fold can be loaded lazily.

<img src="images/shirt.jpg" loading="lazy"
     alt="a brown polo shirt"
     width="500" height="600">

Key points:

  • srcPoints to the actual image resource. -loading="lazy"Requests the browser to delay loading this image. -altProvide image alternative text. -widthandheightHelp the browser reserve layout space.

Container query: Component switches layout according to its own space

(06:35) The same product card will appear in the sidebar, main content area and grid. After using Container Queries, layout judgment is written inside the component.

/* Container queries */

.container {
  container-type: inline-size;
  container-name: clothing-card;
}
.content {
  display: grid;
  grid-template-rows: 1fr;
  gap: 1rem;
}
@container clothing-card (width > 250px) {
  .content {
    grid-template-columns: 1fr 1fr;
  }
  /* additional layout code */
}

Key points:

  • .containeris the container element being measured. -container-type: inline-sizeRepresents the inline direction size of the query container. -container-name: clothing-cardGive the container a name to make it easier to use@containerquoted in. -.contentFirst define the basic one-column grid layout. -@container clothing-card (width > 250px)Applies inner styles when container width exceeds 250px. -grid-template-columns: 1fr 1frSwitch the content to two columns.

Cascade Layers: Turn style priorities into architecture

(07:14) Cascade Layers allow teams to put tool classes, business coverage, and user default styles into different layers. The order between levels precedes the specificity of individual selectors.

/* Author Styles - Layer A */
@layer utilities {
  div {
    background-color: red;
  }
}

/* Author Styles - Layer B */
@layer customizations {
  div {
    background-color: teal;
  }
}

/* Author Styles - Layer C */
@layer userDefaults {
  div {
    background-color: yellow;
  }
}

Key points:

  • @layer utilitiesDefine the tool style layer. -@layer customizationsDefine the item override style layer. -@layer userDefaultsDefine another set of author style layers.
  • Use the same one inside each layerdivselector.
  • Final priority is determined by the order in which the layers are defined, not the selector differences here.

:has(): The parent element changes style according to the state of the child element

(08:54) In the message form, the entire form changes color when the check box is selected. A common practice in the past was to use JavaScript to add classes to parent elements.:has()Let this state stay directly in CSS.

<!-- :has() pseudo-class -->

<style>
  form:has(input[type="checkbox"]:checked) {
    background: #ff927a;
  }
</style>

<form class="message">
  <textarea rows="5" cols="60" name="text" 
    placeholder="Enter text"></textarea>
  <div class="checkbox">
    <input type="checkbox" value="urgent"> 
    <label>Urgent?</label>
  </div>
  <button>Send Message</button>
</form>

Key points:

  • form:has(...)Matches a form that contains the specified element state. -input[type="checkbox"]:checkedPoints to the selected checkbox. -background: #ff927aChange the background of the parent form to the prompt color. -textareais the text of the message.
  • checkboxvalue="urgent"Express the urgency of this message.

Web App Icon: manifest andapple-touch-icon

(21:13) When the user adds the Web App to the Home Screen, the icon can come from the Web App Manifest.

// Manifest file 

"icons": [
 {
   "src": "orange-icon.png",
    "sizes": "120x120",
    "type": "image/png"
  }
]

Key points:

  • iconsLists icon resources available for installation portals. -srcPoint to the icon file. -sizesMark the icon size. -typeIndicate the image MIME type.

(21:28) If declared in HTML headapple-touch-icon, which will be used for Apple device icons. It was explained in the speech that if you want the manifest icon to take priority, you need to ensure that the HTML head is not definedapple-touch-icon

<!-- HTML head -->

<link rel="apple-touch-icon" href="blue-icon.png" />

Key points:

  • rel="apple-touch-icon"Declare the Home Screen icon used by Apple devices. -href="blue-icon.png"Point to this device-specific icon.
  • This is suitable for providing different icons for iOS and iPadOS than other platforms.

Multi-window synchronization: Broadcast Channel

(22:20) The same clothing exchange website may be open in two windows at the same time. One window has received the product, and the other window needs to know that the product is no longer available.

// State change
broadcastChannel.postMessage("Item is unavailable");

Key points:

  • broadcastChannelRepresents a communication channel between originating browsing contexts. -postMessage(...)Send status changes to other originating pages.
  • String message expressing “Item is no longer available”.

Origin private file system: Site private files

(22:49) The origin private file system of the File System Access API gives each origin independent private storage space. An example of a draft document for a speech demonstrates the entrance to reading and writing.

// Accessing the origin private file system
const root = await navigator.storage.getDirectory();

// Create a file named Draft.txt under root directory
const draftHandle = await root.getFileHandle("Draft.txt", { "create": true });

// Access and read an existing file
const existingHandle = await root.getFileHandle("Draft.txt");
const existingFile = await existingHandle.getFile();

Key points:

  • navigator.storage.getDirectory()Get the root directory of the current origin. -root.getFileHandle("Draft.txt", { "create": true })Create or get a handle to a draft file. -root.getFileHandle("Draft.txt")Retrieve a handle to an existing file. -existingHandle.getFile()Read from file handleFileobject.

Shared Worker: Same-origin page sharing background tasks

(25:08) When multiple homologous pages require background calculation, they can share a worker to reduce the memory pressure caused by duplicate workers.

// Create Shared Worker
let worker = new SharedWorker("SharedWorker.js");

// Listen for messages from Shared Worker
worker.port.addEventListener("message", function(event) {
  console.log("Message received from worker: " + event);
});

// Send messages to Shared Worker
worker.port.postMessage("Send message to worker");

Key points:

  • new SharedWorker("SharedWorker.js")Create a shared worker. -worker.port.addEventListener("message", ...)Listen for messages sent back by workers. -console.log(...)Output content when a message is received. -worker.port.postMessage(...)Send messages to workers through the port.

Array tail operation:findLast()findLastIndex()andat()

(25:56) When searching from the end of the array, it is not necessary to firstreverse()Change the array.

const list = ["shirt","pants","shoes","hat","shoestring","dress"];
const hasShoeString = (string) => string.includes("shoe");

console.log(list.findLast(hasAppString));
// shoestring

console.log(list.findLastIndex(hasAppString));
// 4

Key points:

  • listIs an array of product names to be searched. -hasShoeStringis a match containingshoecallback function. -findLast(...)Find the first match from the end of the array forward. -findLastIndex(...)Find the index of the first matching item forward from the end of the array.
  • Call name writing in code snippetshasAppString;The previously declared matching function should be passed in according to the context.

26:17at()Supports negative indexes, suitable for reading elements near the tail.

const list = ["shirt","pants","shoes","hat","shoestring","dress"];

// Instead of this:
console.log(list[list.length - 2]);

// It's as easy as:
console.log(list.at(-2));

Key points:

  • list[list.length - 2]Calculate the penultimate element using its length. -list.at(-2)Read the second to last element directly.
  • Negative indexes make tail accesses shorter and also reduce handwriting length calculations.

CSP Level 3:strict-dynamic

(29:04) Content Security Policy Level 3strict-dynamicAllows trusting a script with a nonce, and then extending trust to subsequent scripts loaded by this script, reducing the explicit domain name allow list.

// strict-dynamic source expression

// Without strict-dynamic
Content-Security-Policy: script-src desired-script.com dependent-script-1.com
  dependent-script-2.com dependent-script-3.com; default-src "self";

// With strict-dynamic
Content-Security-Policy: default-src "self"; script-src "nonce-desired" "strict-dynamic";

Key points:

  • The first policy needs to list the target script and multiple dependent script domain names.
  • A long allow list can easily expand the scope of scripts that are allowed to be loaded.
  • The second policy uses a nonce to mark trusted scripts. -strict-dynamicPass trust to scripts loaded by trusted scripts.

Core Takeaways

  • Make an accessibility-first product request pop-up: use<dialog>To manage the pop-up window structure, use::backdropTo manage masks, useinertExclude non-current carousel items. The entry point starts with the most complex custom modal on the site.

  • Rewrite a reusable product card component: Mark the component container ascontainer-type: inline-size,use@containerControl narrow column, main column and hero card layout. In this way, when the component is moved to a different area, there is no need to add a new page-level media query.

  • Move form state styles from JavaScript back to CSS: Useform:has(input[type="checkbox"]:checked)Handle emergency messages, agree to agreements, and filter items to select this type of parent status. First choose a JS class that is written only to change the style and switch the logic to replace it.

  • Add status broadcast to multi-window Web App: called at status change points such as inventory, drafts, and notification settingsbroadcastChannel.postMessage(...), to keep the same origin window consistent. Suitable for management backend, shopping cart and collaboration tools.

  • Add private draft storage for Web App: usenavigator.storage.getDirectory()Obtain the origin private file system root directory and usegetFileHandle()Save unpublished content. Good for article editors, form drafts, and local caching.

Comments

GitHub Issues · utterances