View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vidéos

Ouvrir le menu Fermer le menu
  • Collections
  • Toutes les vidéos
  • À propos

Plus de vidéos

  • À propos
  • Résumé
  • Code
  • Créez des extensions web pour Safari

    Démarrez avec les extensions web Safari en créant et en testant une de A à Z, sans utiliser Xcode. Découvrez comment le blocage de contenu, la modification des pages, la messagerie native et le modèle Autorisations fonctionnent ensemble pour créer une expérience de navigation puissante et respectueuse de la vie privée sur toutes les plateformes.

    Chapitres

    • 0:00 - Introduction
    • 3:23 - Get started
    • 7:23 - Block content
    • 14:40 - Modify webpages
    • 19:53 - Package and distribute
    • 22:33 - Communicate with your app
    • 26:04 - Next steps

    Ressources

    • w3.org — W3C WebExtensions Community Group
    • Packaging and distributing Safari Web Extensions with App Store Connect
    • WebKit.org – Report issues to the WebKit open-source project
    • Submit feedback
    • MDN Web Docs - Web Extensions API
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC26

    • Nouveautés de WebKit pour Safari 27
  • Rechercher dans cette vidéo…
    • 3:44 - Manifest file

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0
      }
    • 4:29 - Adding an extension icon

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          }
      }
    • 5:30 - Adding an action button

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "action": {
              "default_popup": "popup.html"
          }
      }
    • 6:17 - Adding custom UI to your extension

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
        
          "options_ui": {
              "page": "options.html"
          }
      }
    • 6:30 - Including the UI in the extension manifest

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          },
      
          "options_ui": {
              "page": "options.html"
          }
      }
    • 6:40 - Hello World

      <!DOCTYPE html>
      <html>
          <body>
          <p>Hello World</p>
          </body>
      </html>
    • 8:18 - Adding declarativeNetRequest permission

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          },
      
          "options_ui": {
              "page": "options.html"
          },
        
          "permissions": [ "declarativeNetRequest" ]
      }
    • 8:22 - Blocking network requests

      // block rule
      {
          id: 1,
          priority: 1,
          action: {
              type: "block"
          },
          condition: {
              urlFilter: "||webkit.org",
              resourceTypes: [ "main_frame" ]
          }
      }
    • 8:41 - Modifying network requests

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          },
      
          "options_ui": {
              "page": "options.html"
          },
        
          "permissions": [ "declarativeNetRequest" ],
      
          "declarativeNetRequest": {
              "rule_resources": [
                  {
                      "id": "ruleset_id",
                      "enabled": true,
                      "path": "rules.json"
                  }
              ]
          }
      }
    • 8:50 - Updating dynamic rules

      await browser.declarativeNetRequest.updateDynamicRules({
          addRules: [ rule ]
      })
    • 9:19 - Wiring up the static declarativeNetRequest rules

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          },
      
          "options_ui": {
              "page": "options.html"
          },
        
          "permissions": [ 
            "declarativeNetRequest" 
          ]
      }
    • 9:40 - Adding block rules dynamically

      // A helper function to map the host to the declarative net request rule ID.
      export function hostToRuleID(host) {
      	let hash = 0;
      	for (let i = 0; i < host.length; i++) {
      		hash = ((hash << 5) + hash) + host.charCodeAt(i);
      		hash |= 0;
      	}
      	return Math.abs(hash) || 1;
      }
      
      function createBlockRule(host) {
      	return {
      		id: hostToRuleID(host),
      		priority: 1,
      		action: {
      			type: "block"
      		},
      		condition: {
      			urlFilter: `||${host}`,
      			resourceTypes: ["main_frame"]
      		}
      	}
      }
      
      export async function createRules(hosts) {
      	try {
      		await browser.declarativeNetRequest.updateDynamicRules({
      			addRules: hosts.map(createBlockRule)
      		})
      	} catch {
      		console.log("Failed to create declarative net request rules")
      	}
      }
    • 10:10 - Handling adding hosts to the settings

      import { createRules, removeAllRules, removeRule } from './rules.js'
      
      export async function addHost(host, blockingMode) {
        if (!host)
          return
        
        if (blockingMode === "full")
          await createRules([host])
      }
    • 10:48 - Redirecting network requests

      {
          id: 1,
          priority: 1,
          action: {
              type: "redirect",
              redirect: {
                  extensionPath: "/blocked.html"
              }
          },
          condition: {
              urlFilter: "||webkit.org",
              resourceTypes: [ "main_frame" ]
          }
      }
    • 11:17 - Declaring optional host permissions

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          },
      
          "options_ui": {
              "page": "options.html"
          },
        
          "permissions": [ "declarativeNetRequestWithHostAccess" ],
          "optional_host_permissions": [ "https://webkit.org/*" ]
      
      }
    • 11:54 - Declaring optional host permissions for all sites

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          },
      
          "options_ui": {
              "page": "options.html"
          },
        
          "permissions": [ "declarativeNetRequestWithHostAccess" ],
          "optional_host_permissions": [ "*://*/*" ]
      
      }
    • 13:12 - Add the redirect rule

      // A helper function to map the host to the declarative net request rule ID.
      export function hostToRuleID(host) {
      	let hash = 0;
      	for (let i = 0; i < host.length; i++) {
      		hash = ((hash << 5) + hash) + host.charCodeAt(i);
      		hash |= 0;
      	}
      	return Math.abs(hash) || 1;
      }
      
      function createBlockRule(host) {
      	return {
      		id: hostToRuleID(host),
      		priority: 1,
      		action: {
      			type: "block"
      		},
      		condition: {
      			urlFilter: `||${host}`,
      			resourceTypes: ["main_frame"]
      		}
      	}
      }
      
      function createRedirectRule(host) {
      	return {
      		id: hostToRuleID(host),
      		priority: 1,
      		action: {
      			type: "redirect",
      			redirect: { extensionPath: "/blocked.html" }
      		},
      		condition: {
      			urlFilter: `||${host}`,
      			resourceTypes: ["main_frame"]
      		}
      	}
      }
      
      export async function createRules(hosts) {
      	try {
      		await browser.declarativeNetRequest.updateDynamicRules({
      			addRules: hosts.map(createRedirectRule)
      		})
      	} catch {
      		console.log("Failed to create declarative net request rules")
      	}
      }
    • 13:42 - Dynamically ask for host permissions

      import { createRules, removeAllRules, removeRule } from './rules.js'
      
      export async function addHost(host, blockingMode) {
        if (!host)
          return
        
        const granted = await browser.permissions.request({
          origins: [`*://${host}/*`, `*://*.${host}/*`]
        })
        if (!granted)
          return
        
        if (blockingMode === "full")
          await createRules([host])
      }
    • 14:55 - Defining content scripts

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
      
          "icons": {
              "512": "images/icon.svg"
          },
      
          "options_ui": {
              "page": "options.html"
          },
        
          "permissions": [ "declarativeNetRequestWithHostAccess" ],
          "optional_host_permissions": [ "*://*/*" ],
        
          "content_scripts": [
              {
                  "js": [ "content.js" ],
                  "css": [ "content.css" ],
                  "matches": [ "*://*.webkit.org/*" ]
              }
          ]
      }
    • 15:13 - Dynamically registering content scripts

      let script = {
          id: "id",
          js: [ "content.js" ],
          css: [ "content.css" ],
          matches: [ "*://*.webkit.org/*" ],
          persistAcrossSessions: true
      }
      
      await browser.scripting.registerContentScripts([ script ])
    • 15:31 - Adding the scripting permission

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
        
          "icons": {
              "512": "images/icon.svg"
          },
        
          "options_page": "options.html",
        
          "permissions": [
              "declarativeNetRequestWithHostAccess",
              "scripting"
          ],
        
          "optional_host_permissions": [ "*://*/*" ]
      }
    • 15:41 - Registering content scripts

      // scripting.js
      
      function contentScript(host) {
          return {
              id: `cs-${host}`,
              js: [ "content.js" ],
              css: [ "content.css" ],
              matches: [ `*://${host}/*`, `*://*.${host}/*` ],
              persistAcrossSessions: true
          }
      }
      
      export function registerScripts(hosts) {
          const scripts = hosts.map(contentScript)
          try {
              await browser.scripting.registerContentScripts(scripts)
          } catch {
              console.log("Failed to register content scripts")
          }
      }
    • 16:02 - Adding a host

      // host.js
      
      export async function addHost(host, blockMode) {
          if (!host)
              return
      
          const granted = await browser.permissions.request({
              origins: [`*://${host}/*`, `*://*.${host}/*`]
          })
      
          if (!granted)
              return
      
          if (blockingMode === "full")
              await createRules([ host ])
      
          await registerScripts([ host ])
      }
    • 17:06 - Web extensions storage APIs

      await browser.session.storage.set({
        key: value
      })
      
      await browser.local.storage.set({
        key: value
      })
    • 17:21 - Adding storage permission to the web extension manifest.json

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
        
          "icons": {
              "512": "images/icon.svg"
          },
        
          "options_page": "options.html",
        
          "permissions": [
              "declarativeNetRequestWithHostAccess",
              "scripting",
              "storage"
          ],
        
          "optional_host_permissions": [ "*://*/*" ]
      }
    • 17:30 - Saving data with storage

      // storage.js
      
      export async function updateHosts(hosts) {
          await browser.storage.local.set({ hosts: hosts })
      }
      
      export async function getHosts() {
          const { hosts = [] } = await browser.storage.local.get("hosts")
          return hosts
      }
      
      export async function saveBlockMode(mode) {
          await browser.storage.local.set({ blockMode: mode })
      }
      
      export async function getBlockMode() {
          const { blockMode = "full" } = await browser.storage.local.get("blockMode")
          return blockMode
      }
    • 17:41 - Persisting hosts to storage

      // host.js
      
      export async function addHost(host, blockMode) {
          if (!host)
              return
      
          const granted = await browser.permissions.request({
              origins: [`*://${host}/*`, `*://*.${host}/*`]
          })
      
          if (!granted)
              return
      
          if (blockingMode === "full")
              await createRules([ host ])
      
          await registerScripts([ host ])
      
          let existingHosts = await getHosts()
          let updatedHosts = [ ...existingHosts, host ]
          await updateHosts(updatedHosts)
      }
    • 17:51 - Reading from storage

      // options.js
      
      let existingHosts = await getHosts()
      let blockMode = await getBlockMode()
      
      displayBlocklist(existingHosts)
    • 18:00 - Switching block modes

      // host.js
      
      export async function userDidSwitchMode(blockMode) {
          await saveBlockMode(blockMode)
      
          if (blockMode === "full") {
              let hosts = await getHosts()
              await createRules(hosts)
          } else
              await removeAllRules()
      }
    • 19:01 - Adding a background script

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
        
          "icons": {
              "512": "images/icon.svg"
          },
        
          "options_page": "options.html",
        
          "permissions": [
              "declarativeNetRequestWithHostAccess",
              "scripting",
              "storage"
          ],
        
          "optional_host_permissions": [ "*://*/*" ],
        
          "background": {
              "scripts": [ "background.js" ],
              "type": "module"
          }
      }
    • 19:39 - Background script

      // background.js
      
      import { registerScripts } from "./utilities/scripting.js"
      import { getHosts } from "./utilities/storage.js"
      
      browser.runtime.onInstalled.addListener(async (details) => {
          if (details.reason !== "update")
              return
      
          const hosts = await getHosts()
          await registerScripts(hosts)
      })
    • 22:49 - Package your web extension into an app for Xcode

      xcrun safari-web-extension-packager --copy-resources /path/to/ShinyOnTrack
    • 23:32 - Adding the nativeMessaging permission

      {
          "manifest_version": 3,
          "name": "Shiny OnTrack",
          "description": "Stay on track while you browse the web",
          "version": 1.0,
        
          "icons": {
              "512": "images/icon.svg"
          },
        
          "options_page": "options.html",
        
          "permissions": [
              "declarativeNetRequestWithHostAccess",
              "scripting",
              "storage",
              "nativeMessaging"
          ],
        
          "optional_host_permissions": [ "*://*/*" ],
        
          "background": {
              "scripts": [ "background.js" ],
              "type": "module"
          }
      }
    • 23:40 - Sending a native message

      // background.js
      
      import { registerScripts } from "./utilities/scripting.js"
      import { getHosts } from "./utilities/storage.js"
      
      browser.runtime.onInstalled.addListener(async (details) => {
          if (details.reason !== "update")
              return
      
          const hosts = await getHosts()
          await registerScripts(hosts)
      })
      
      export async function requestBioAuth() {
          const message = { message: "requestBioAuth" }
          const response = await browser.runtime.sendNativeMessage(message)
          return response?.success
      }
    • 23:55 - Handling native messages

      // SafariWebExtensionHandler.swift
      
      import LocalAuthentication
      
      class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
          func beginRequest(with context: NSExtensionContext) {
              let request = context.inputItems.first as? NSExtensionItem
              let message = request?.userInfo?[SFExtensionMessageKey] as? [String: Any]
      
              if message?["message"] as? String == "requestBioAuth" {
                  let lAContext = LAContext()
                  Task {
                      do {
                          let success = try await lAContext.evaluatePolicy(
                              .deviceOwnerAuthenticationWithBiometrics,
                              localizedReason: "Authenticate to change blocked sites"
                          )
                          self.reply(context: context, success: success)
                      } catch {
                          self.reply(context: context, success: false)
                      }
                  }
              }
          }
      }
    • 24:25 - Replying to a native message

      // SafariWebExtensionHandler.swift
      
      import LocalAuthentication
      
      class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
          func beginRequest(with context: NSExtensionContext) {
              let request = context.inputItems.first as? NSExtensionItem
              let message = request?.userInfo?[SFExtensionMessageKey] as? [String: Any]
      
              if message?["message"] as? String == "requestBioAuth" {
                  let lAContext = LAContext()
                  Task {
                      do {
                          let success = try await lAContext.evaluatePolicy(
                              .deviceOwnerAuthenticationWithBiometrics,
                              localizedReason: "Authenticate to change blocked sites"
                          )
                          self.reply(context: context, success: success)
                      } catch {
                          self.reply(context: context, success: false)
                      }
                  }
              }
          }
      
          private func reply(context: NSExtensionContext, success: Bool) {
              let response = NSExtensionItem()
              response.userInfo = [SFExtensionMessageKey: ["success": success]]
              context.completeRequest(returningItems: [response], completionHandler: nil)
          }
      }
    • 0:00 - Introduction
    • Learn how Safari web extensions — built with HTML, CSS, and JavaScript and packaged inside an app — can run across iOS, iPadOS, macOS, and visionOS. Preview the distraction-blocker extension built throughout the session, which offers a 10-minute light mode and a full redirect mode.

    • 3:23 - Get started
    • Set up an extension from scratch by writing a manifest.json file, then add a popup UI so the extension is reachable from Safari's toolbar. The same project runs unchanged across every Apple platform that ships Safari.

    • 7:23 - Block content
    • Use the declarativeNetRequest API to block, modify, and redirect network requests, and declare the host permissions — including optional host permissions — that let users grant access on the sites where the extension should run.

    • 14:40 - Modify webpages
    • Inject content into pages with content scripts to render a countdown timer on distracting sites. Register scripts dynamically with the scripting API and persist user preferences and per-host state using the storage API and a background service worker.

    • 19:53 - Package and distribute
    • Submit a Safari web extension to the App Store using App Store Connect, and share beta builds with testers through TestFlight.

    • 22:33 - Communicate with your app
    • Generate an Xcode project with the Safari Web Extension Packager, then use native messaging to pass requests between the JavaScript extension and its containing app — unlocking platform features like Local Authentication that aren't available to web APIs.

    • 26:04 - Next steps
    • Download the sample project, explore the cross-browser WebExtensions documentation on MDN, and file feedback through Feedback Assistant or bugs.webkit.org.

Developer Footer

  • Vidéos
  • WWDC26
  • Créez des extensions web pour Safari
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines