Safari Web Extension does not load content script on iOS 15

The content scripts specified in the manifest of an extension should be loaded each time a user visits a web page. However, in the iOS 15 simulator, the content script is only loaded the first time that the extension is enabled -- either when you grant permission for the site, or if you turn the extension off and back on while on a site. Otherwise, the content script is not injected. This is clearly a bug, right? Is there a known workaround or a fix in the works?

I had this exact same issue for a while, you want to make sure the permissions are enabled on the website or the content script won't inject. I still had the issue at random times, but after updating to the beta 2 it was much more consistent.

I am seeing the same problem in Safari 14.1.2 for Big Sur.

It appears that content.js won't be injected into web pages that you have not given permission for. I am noticing that on iOS and macOS I have to go searching for a tiny yellow caution icon when permissions are set to 'Ask'. Selecting this caution icon, an alert-like box pops up asking to give permission for one day or permanently.

I could not find the injected scripts in the debugging tools Sources, until after I gave permission. Then mysteriously, the extension started working again.

Same problem... same diagnostic Works fine on simulator 15.2 Does not work correctly on iOS15.4

  • extension enabled
  • extension authorized for any url and allowed/always, except on the first page loaded after Safari is killed and restarted
  • content script won't load (and is clearly not loaded, since it's not even appearing in "sources" when you "remotely" check sources from Safari on Mac using Development tools)
  • background script is not called back on browser.webNavigation.onBeforeNavigate

I will probably file a bug report...

Warning in console (but there are so many warnings, who can say which one is significant):

How to communicate between Content Script, Popup, and Background in Browser Extension development

Message passing between different parts of a browser extension is the most confusing part when starting with browser extension development. This post is about how I generally structure my web extensions code to communicate between Content Script, Background, and popups (Browser Action).

These are the pieces we will use.

  • Create IDs for the messages we will be using in a file. We can use regular object literals or enum if you use TypeScript.
  • Create a mapping ( Map<MessageID, callback> or regular object literal ) where we fix the message id which we created in Step 1 with a callback to run when the message with that ID arrives.
  • Register message listeners. Loop through the items in the Map we created in Step 2 and add a listener which is the value for each key ( MessageID ).

Let's write some code

The finished code is available in my GitHub at web-extension-communication-blog-post . I recommend you open the link and follow along with me. We will also use a polyfill so we don't have to deal with the API differences between Firefox and Chrome. Also, the polyfill allows Promise-based API for both Firefox and Chrome. I am using webextension-polyfill-ts which is a TypeScript wrapper for Mozilla's webextension-polyfill .

Our messages will be simple. We will exchange "Hi" or "Bye" between Content Script and Background.

First, we will write two utility functions that we can use to send messages between Content Script and Background.

I like to put these two functions in a separate file because we don't have to constantly throw in browser.tabs or browser.runtime API everywhere. We get cleaner code with Messenger.sendMessageToBackground and Messenger.sendMessageToContentScript functions.

Remember I told you in Step 1 that we will create IDs for each type of messages, let's define those. I am using TypeScript enums because they are easy to type the functions, but you can use simple objects as well. The IDs can be simple numbers - 1, 2, etc.,

Now whenever we need to talk to Background script from Content Script or Popup, we can write Messenger.sendMessageToBackground(BackgroundMessages.SAY_HELLO_TO_BG, {message: "Hey Background"}) .

Sending message to Content Script from Background is also similar with a difference that we need to pass the tab ID of the content script. That is the first parameter you see in Messenger.sendMessageToContentScript(tabID, ContentScriptMessages.SAY_HELLO_TO_CS, {message: "Hey Content Script!"}) function.

Registering Message listeners

We will register the message listeners that we talked about in Step 2 and 3. This code is similar for both Content Script and Background. We will register ContentScriptMessages in content script initialization and BackgroundMessages in background initialization.

We can use the messages.ts and Messenger.ts from popup as well. Since popup won't be open all the time, we don't have to add message listeners there. I prefer to use the Messenger.sendMessageToBackground and use the return value in the popup.

We've abstracted most of the messaging to messages.ts and Messenger.ts . Whenever you want to add new types of messages, update the enum (or add a key if you used objects) in messages.ts and add a listener in the content script or background in their registerMessengerRequests function.

This code works in Firefox and all chromium-based browsers. Simply send a message and await for the response if the other side returns something from the listener. Thanks to Mozilla's webextension-polyfill we get cross-browser support and don't have to deal with the callback version of Chrome's API.

There are other ways people are trying to solve this like webext-redux which is a clever way for message passing along with managing state between different parts of the extension the redux way. But I feel it adds additional verbose API in an attempt to solve existing complexity and only works with React. Feel free to check that repository if that suits your requirements.

You can install the sample extension I built for this blog post here .

Have a great day !!! 👋

Navigation Menu

Search code, repositories, users, issues, pull requests..., provide feedback.

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly.

To see all available qualifiers, see our documentation .

  • Notifications You must be signed in to change notification settings

A minimal-reproducable example of a Safari Web Extension bug I've run into where the non-persistant background script seems to crash after 30 seconds even when the content script is messaging it.

alexkates/content-script-non-responsive-bug

Folders and files, repository files navigation, content script non-responsive bug.

A minimal-reproducible example of a Safari Web Extension bug I've run into where the non-persistant background script seems to crash after 30 seconds even when the content script is messaging it. Apple Developer Forums Post

Steps to Reproduce

  • Clone this repo
  • Open in Xcode and run targeting a physical device
  • Open Safari on the device and enable the extension
  • Open a new tab and navigate to https://www.google.com
  • From your Mac, open the Safari Web Inspector and navigate to the extension's background page
  • You will see messages being sent from the content script to the background script every 1 second
  • After 30 seconds, the background script will stop receiving messages. Trying to use the console to interact with the background script, e.g., typing console.log("hello") will show that it is no longer responsive.

Other Notes

  • This issue does not occur when running the extension in the simulator
  • Swift 61.5%
  • JavaScript 21.2%
  • Skip to main content
  • Skip to search
  • Skip to select language
  • Sign up for free
  • Remember language

Content scripts

A content script is a part of your extension that runs in the context of a web page (as opposed to background scripts that are part of the extension, or scripts that are part of the website itself, such as those loaded using the <script> element).

Background scripts can access all the WebExtension JavaScript APIs , but they can't directly access the content of web pages. So if your extension needs to do that, you need content scripts.

Just like the scripts loaded by normal web pages, content scripts can read and modify the content of their pages using the standard Web APIs . However, they can only do this when host permissions to the web page's origin have been granted .

Note: Some Web APIs are restricted to secure contexts , which also applies to content scripts running in these contexts. Except for PointerEvent.getCoalescedEvents() , which can be called from content scripts in insecure contexts in Firefox.

Content scripts can only access a small subset of the WebExtension APIs , but they can communicate with background scripts using a messaging system, and thereby indirectly access the WebExtension APIs.

Loading content scripts

You can load a content script into a web page:

  • Using the content_scripts key in your manifest.json , you can ask the browser to load a content script whenever the browser loads a page whose URL matches a given pattern .
  • Using scripting.registerContentScripts() or (only in Manifest V2 in Firefox) contentScripts , you can ask the browser to load a content script whenever the browser loads a page whose URL matches a given pattern . (This is similar to method 1, except that you can add and remove content scripts at runtime.)
  • Using scripting.executeScript() or (in Manifest V2 only) tabs.executeScript() , you can load a content script into a specific tab whenever you want. (For example, in response to the user clicking on a browser action .)

There is only one global scope per frame, per extension . This means that variables from one content script can directly be accessed by another content script, regardless of how the content script was loaded.

Using methods (1) and (2), you can only load scripts into pages whose URLs can be represented using a match pattern .

Using method (3), you can also load scripts into pages packaged with your extension, but you can't load scripts into privileged browser pages (like " about:debugging " or " about:addons ").

Note: Dynamic JS module imports are now working in content scripts. For more details, see Firefox bug 1536094 . Only URLs with the moz-extension scheme are allowed, which excludes data URLs ( Firefox bug 1587336 ).

Permissions, restrictions, and limitations

Permissions.

Registered content scripts are only executed if the extension is granted host permissions for the domain.

To inject scripts programmatically, the extension needs either the activeTab permission or host permissions . The scripting permission is required to use methods from the scripting API.

Starting with Manifest V3, host permissions are not automatically granted at install time. Users may opt in or out of host permissions after installing the extension.

Restricted domains

Both host permissions and the activeTab permission have exceptions for some domains. Content scripts are blocked from executing on these domains, for example, to protect the user from an extension escalating privileges through special pages.

In Firefox, this includes the following domains:

  • accounts-static.cdn.mozilla.net
  • accounts.firefox.com
  • addons.cdn.mozilla.net
  • addons.mozilla.org
  • api.accounts.firefox.com
  • content.cdn.mozilla.net
  • discovery.addons.mozilla.org
  • install.mozilla.org
  • oauth.accounts.firefox.com
  • profile.accounts.firefox.com
  • support.mozilla.org
  • sync.services.mozilla.com

Other browsers have similar restrictions over the websites extensions can be installed from. For example, access to chrome.google.com is restricted in Chrome.

Note: Because these restrictions include addons.mozilla.org, users who try to use your extension immediately after installation may find that it doesn't work. To avoid this, you should add an appropriate warning or an onboarding page to move users away from addons.mozilla.org .

The set of domains can be restricted further through enterprise policies: Firefox recognizes the restricted_domains policy as documented at ExtensionSettings in mozilla/policy-templates . Chrome's runtime_blocked_hosts policy is documented at Configure ExtensionSettings policy .

Limitations

Whole tabs or frames may be loaded using data: URI , Blob objects, and other similar techniques. Support of content scripts injection into such special documents varies across browsers, see the Firefox bug #1411641 comment 41 for some details.

Content script environment

Content scripts can access and modify the page's DOM, just like normal page scripts can. They can also see any changes that were made to the DOM by page scripts.

However, content scripts get a "clean" view of the DOM. This means:

  • Content scripts cannot see JavaScript variables defined by page scripts.
  • If a page script redefines a built-in DOM property, the content script sees the original version of the property, not the redefined version.

As noted at "Content script environment" at Chrome incompatibilities , the behavior differs across browsers:

  • In Firefox, this behavior is called Xray vision . Content scripts may encounter JavaScript objects from its own global scope or Xray-wrapped versions from the web page.
  • In Chrome this behavior is enforced through an isolated world , which uses a fundamentally different approach.

Consider a web page like this:

The script page-script.js does this:

Now an extension injects a content script into the page:

The same is true in reverse; page scripts cannot see JavaScript properties added by content scripts.

This means that content scripts can rely on DOM properties behaving predictably, without worrying about its variables clashing with variables from the page script.

One practical consequence of this behavior is that a content script doesn't have access to any JavaScript libraries loaded by the page. So, for example, if the page includes jQuery, the content script can't see it.

If a content script needs to use a JavaScript library, then the library itself should be injected as a content script alongside the content script that wants to use it:

Note: Firefox provides cloneInto() and exportFunction() to enable content scripts to access JavaScript objects created by page scripts and expose their JavaScript objects to page scripts.

See Sharing objects with page scripts for more details.

WebExtension APIs

In addition to the standard DOM APIs, content scripts can use the following WebExtension APIs:

From extension :

  • inIncognitoContext

From runtime :

  • getManifest()
  • sendMessage()

From i18n :

  • getMessage()
  • getAcceptLanguages()
  • getUILanguage()
  • detectLanguage()

From menus :

  • getTargetElement

Everything from:

XHR and Fetch

Content scripts can make requests using the normal window.XMLHttpRequest and window.fetch() APIs.

Note: In Firefox in Manifest V2, content script requests (for example, using fetch() ) happen in the context of an extension, so you must provide an absolute URL to reference page content.

In Chrome and Firefox in Manifest V3, these requests happen in context of the page, so they are made to a relative URL. For example, /api is sent to https://«current page URL»/api .

Content scripts get the same cross-domain privileges as the rest of the extension: so if the extension has requested cross-domain access for a domain using the permissions key in manifest.json , then its content scripts get access that domain as well.

Note: When using Manifest V3, content scripts can perform cross-origin requests when the destination server opts in using CORS ; however, host permissions don't work in content scripts, but they still do in regular extension pages.

This is accomplished by exposing more privileged XHR and fetch instances in the content script, which has the side effect of not setting the Origin and Referer headers like a request from the page itself would; this is often preferable to prevent the request from revealing its cross-origin nature.

Note: In Firefox in Manifest V2, extensions that need to perform requests that behave as if they were sent by the content itself can use content.XMLHttpRequest and content.fetch() instead.

For cross-browser extensions, the presence of these methods must be feature-detected.

This is not possible in Manifest V3, as content.XMLHttpRequest and content.fetch() are not available.

Note: In Chrome, starting with version 73, and Firefox, starting with version 101 when using Manifest V3, content scripts are subject to the same CORS policy as the page they are running within. Only backend scripts have elevated cross-domain privileges. See Changes to Cross-Origin Requests in Chrome Extension Content Scripts .

Communicating with background scripts

Although content scripts can't directly use most of the WebExtension APIs, they can communicate with the extension's background scripts using the messaging APIs, and can therefore indirectly access all the same APIs that the background scripts can.

There are two basic patterns for communicating between the background scripts and content scripts:

  • You can send one-off messages (with an optional response).
  • You can set up a longer-lived connection between the two sides , and use that connection to exchange messages.

One-off messages

To send one-off messages, with an optional response, you can use the following APIs:

For example, here's a content script that listens for click events in the web page.

If the click was on a link, it sends a message to the background page with the target URL:

The background script listens for these messages and displays a notification using the notifications API:

(This example code is lightly adapted from the notify-link-clicks-i18n example on GitHub.)

Connection-based messaging

Sending one-off messages can get cumbersome if you are exchanging a lot of messages between a background script and a content script. So an alternative pattern is to establish a longer-lived connection between the two contexts, and use this connection to exchange messages.

Both sides have a runtime.Port object, which they can use to exchange messages.

To create the connection:

  • One side listens for connections using runtime.onConnect
  • tabs.connect() (if connecting to a content script)
  • runtime.connect() (if connecting to a background script)

This returns a runtime.Port object.

  • The runtime.onConnect listener gets passed its own runtime.Port object.

Once each side has a port, the two sides can:

  • Send messages using runtime.Port.postMessage()
  • Receive messages using runtime.Port.onMessage()

For example, as soon as it loads, the following content script:

  • Connects to the background script
  • Stores the Port in a variable myPort
  • Listens for messages on myPort (and logs them)
  • Uses myPort to sends messages to the background script when the user clicks the document

The corresponding background script:

  • Listens for connection attempts from the content script
  • Stores the port in a variable named portFromCS
  • Sends the content script a message using the port
  • Starts listening to messages received on the port, and logs them
  • Sends messages to the content script, using portFromCS , when the user clicks the extension's browser action

Multiple content scripts

If you have multiple content scripts communicating at the same time, you might want to store connections to them in an array.

Choosing between one-off messages and connection-based messaging

The choice between one-off and connection-based messaging depends on how your extension expects to make use of messaging.

The recommended best practices are:

  • Only one response is expected to a message.
  • A small number of scripts listen to receive messages ( runtime.onMessage calls).
  • Scripts engage in sessions where multiple messages are exchanged.
  • The extension needs to know about task progress or if a task is interrupted, or wants to interrupt a task initiated using messaging.

Communicating with the web page

By default, content scripts don't get access to the objects created by page scripts. However, they can communicate with page scripts using the DOM window.postMessage and window.addEventListener APIs.

For example:

For a complete working example of this, visit the demo page on GitHub and follow the instructions.

Warning: Be very careful when interacting with untrusted web content in this manner! Extensions are privileged code which can have powerful capabilities and hostile web pages can easily trick them into accessing those capabilities.

To give a trivial example, suppose the content script code that receives the message does something like this:

Now the page script can run any code with all the privileges of the content script.

Using eval() in content scripts

Note: eval() not available in Manifest V3.

eval always runs code in the context of the content script , not in the context of the page.

If you call eval() , it runs code in the context of the content script .

If you call window.eval() , it runs code in the context of the page .

For example, consider a content script like this:

This code just creates some variables x and y using window.eval() and eval() , logs their values, and then messages the page.

On receiving the message, the page script logs the same variables:

In Chrome, this produces output like this:

In Firefox, this produces output like this:

The same applies to setTimeout() , setInterval() , and Function() .

Warning: Be very careful when running code in the context of the page!

The page's environment is controlled by potentially malicious web pages, which can redefine objects you interact with to behave in unexpected ways:

WebKit Features in Safari 18.0

Sep 16, 2024

by Jen Simmons

New in Safari 18

Web apps for mac, spatial web, managed media source, web inspector, safari extensions, deprecations, bug fixes and more, updating to safari 18.0.

Safari 18.0 is here. Along with iOS 18, iPadOS 18, macOS Sequoia and visionOS 2, today is the day another 53 web platform features, as well as 25 deprecations and 209 resolved issues land in WebKit, the rendering engine driving Safari.

Distraction Control

Distraction Control lets you hide distracting items as you browse the web, such as sign-in banners, cookie preference popups, newsletter signup overlays, and more, in Safari for iOS 18, iPadOS 18 and macOS Sequoia.

safari web extension content script

We always recommend using semantic HTML when creating a website, including <video> , <main> , <article> and other elements that describe content. Doing so helps ensure both Safari Reader and Safari Viewer work best for the users of your website.

Safari windows on a Mac, with a video playing, big, in one window. The website is faintly visible behind the large video.

iPhone Mirroring and remote inspection

With iPhone Mirroring on macOS Sequoia, you can use your iPhone from your Mac. Combine it with remote inspection from Safari, and now it’s easier than ever to test and debug websites on iOS using Web Inspector .

Get set up for remote inspection by first ensuring you have Safari’s developer tools enabled on your Mac (if you can see the Develop menu in Safari, you’ve already done this step). Next, enable Web Inspector on your iPhone at Settings > Apps > Safari > Advanced > Web Inspector. Then, you’ll need to connect the device to your Mac using a cable to grant permission. Once plugged in, your device will appear in the Develop menu in Safari. Finally, to enable wireless debugging, go to Safari on macOS > Develop > [your device] > Connect via Network.

Now you can use Web Inspector to wirelessly debug websites running on iPhone anytime. And with iPhone Mirroring, you don’t even have to pull out your phone. Everything is on your Mac’s screen.

Learn more about remote inspection by reading Inspecting iOS and iPadOS , or by watching Rediscover Safari developer features from WWDC. Learn more about iPhone Mirroring 1 on apple.com .

Last year , we added support for web apps in macOS Sonoma. You can add any website to your dock — whether or not it was built with a Manifest file, Service Worker, or other technology to customize the web app experience. Go to the site in Safari, then File > Add to Dock
 where you can customize the icon, change the name, and even adjust the URL. Then, just click on the web app icon in your Dock, and the website will open as a stand-alone app.

This year brings two improvements to web apps on Mac.

Opening links

macOS Sequoia adds support for opening links directly in web apps. Now, when a user clicks a link, if it matches the scope of a web app, that link will open in the web app instead of their default web browser. For example, imagine you have added MDN Web Docs to your Dock. Then a colleague sends you a link to an MDN page in Messages, Mail, Slack, Discord, IRC, or any non-browser application on your Mac. Now when you click on that link, it will open in the MDN Web Docs web app instead of your default browser.

Clicking a link within a browser will maintain the current behavior. This feature only affects links opened elsewhere. (When a user is in Safari, clicking on a link that matches the scope of a web app that is added to Dock, they will see an “Open in web app” banner, unless they have previously dismissed the banner.)

By default, this behavior applies when the link matches the host of the web page used to create the web app. As a developer, you can refine this experience by defining the range of URLs that should open in the web app with the scope member in the web app manifest .

Extension support

Now users can personalize web apps on Mac with Safari Web Extensions and Content Blockers. Navigate to the web app’s Settings menu to access all the installed Content Blockers and Web Extensions. Any enabled in Safari will be on by default in the web app. Each web app is uniquely customizable, just like Safari profiles.

View Transitions

WebKit added support for the View Transitions API in Safari 18. It provides an optimized browser API to animate elements from one state to another. Safari supports the CSS View Transitions Module Level 1 specification that adds new CSS properties and pseudo-elements for defining transition animations, along with a new browser API to start transition animations and react to different transition states. It works by capturing the current (old) state of the page and applying an animated transition to the new state. By default, the browser applies a cross-fade between the states.

Call the document.startViewTransition() method to initiate the capture. You can pass a callback function as the first argument to make DOM state changes between the old and new captures. The method returns a ViewTransition object which contains promises that can be used to track when the view transition starts or ends.

Once the states are captured, a pseudo-element tree is built which can be targeted with CSS, allowing you to modify the CSS animations used for the transitions. The animations out of the old page state and into the new page state can be modified via the ::view-transition-new(*) and ::view-transition-old(*) selectors. You can also ask the browser to independently track state changes for a specific element by naming it with the CSS view-transition-name property. You can then use the pseudo-elements to customize animations for it.

The :active-view-transition pseudo-class becomes active on the root element when a view transition is running.

The example below demonstrates state management with tabbed navigation. Each tab view has a custom transition animation out and a subtly different animation in, while the tabs themselves rely on the default page transition.

Style Queries

WebKit for Safari 18.0 adds support for Style Queries when testing CSS Custom Properties. Similar to how developers can use Sass mixins, Style Queries can be used to define a set of reusable styles that get applied as a group.

Here, if the --background custom property is set to black, then certain styles will be applied — in this case to make the headline and paragraph text color white.

Don’t forget to pay attention the HTML structure. By default, Style Queries reference the styles on the direct parent element. You can create a different reference through the use of Container Query names.

currentcolor and system color keywords in Relative Color Syntax

Support for Relative Color Syntax shipped in Safari 16.4 . It lets you define colors in a more dynamic fashion, creating a new color from an existing color. The value lch(from var(--color) calc(L / 2) C H) for instance uses the lch color space to take the variable --color and calculate a new color that’s half its lightness, calc(L / 2) .

Now in Safari 18.0, the first browser to ship support, you can reference the currentcolor or a system color keyword as you define the new color. For example, this code will set the background color to be the same color as the text color, only 4 times lighter, as calculated in the oklch color space.

Being able to reference system color keywords opens up another world of options. System colors are like variables that represent the default colors established by the OS, browser, or user — defaults that change depending on whether the system is set to light mode, dark mode, high contrast mode, etc. For example, canvas represents the current default background color of the HTML page, while fieldtext matches the color of text inside form fields. Find the full list of system colors in CSS Color level 4 .

Relative Color Syntax lets you define dynamic connections between colors in your CSS, lessening the need to control color through variables in a tightly-regimented design system. Learn more about Relative Color Syntax by watching this portion of What’s new in CSS from WWDC23.

Translucent accent colors

Partially transparent colors in accent-color are now blended on top of the Canvas system color to match the latest updates to the web standard. This means that any of the many ways to define colors using an alpha channel will now work as expected when used to define an accent color for a form control.

Animating display

WebKit for Safari 18.0 adds support for transition animation of the display property.

Many developers are excited to use @starting-style along with transition-behavior and display: none interpolation. WebKit for Safari 17.4 added general support for transition-behavior , including transition-behavior: allow-discrete . WebKit for Safari 17.5 added support for @starting-style , letting you define starting values for transitioning an element as it’s created (or re-created). Now in WebKit for Safari 18.0, you can use these features together to transition the display property.

Backdrop Filter

Originally shipped in Safari 9.0, backdrop filter provides a way to apply graphics effects to the content behind a particular element. You can apply backdrop-filter to a headline, for example, and everything behind the headline will be blurred, or have decreased saturation, or increased contrast. Any of the filter functions from SVG can be used — blur() , brightness() , contrast() , drop-shadow() , grayscale() , hue-rotate() , invert() , opacity() , saturate() , and sepia() .

For many years, backdrop filter only worked in Safari. It was available when you prefixed the property with -webkit-backdrop-filter . Now, starting in Safari 18.0, you don’t need the prefix. We also improved our implementation, fixing bugs and boosting interoperability.

This demo shows eight different filters and what you might do with each one alone. You can, of course, combine filters to create even more interesting results. With backdrop filter supported in Safari since 2015, Edge since 2018, Chrome since 2019, Samsung Internet since 2020, and Firefox since 2022, this is a great time to consider the kind of graphic design possibilities it enables.

Content visibility

WebKit for Safari 18.0 adds support for content-visibility . This property controls whether or not an element renders its contents in a fashion that’s useful for making performance optimizations. It lets you communicate to the browser that certain portions of the page will likely be initially offscreen, and suggest they be omitted from layout and rendering. This can make the page load faster.

WebKit for Safari 18.0 adds parsing support for the custom value for the prefers-contrast media query. (It does not return “true” on Apple platforms, since there is no forced-colors mode in iOS, iPadOS, macOS or visionOS.)

Safari 18.0 for visionOS 2 adds support for immersive-vr sessions with WebXR . Now you can create fully immersive experiences for people using Apple Vision Pro and deliver them through the web. WebXR scenes are displayed using hardware-accelerated graphics driven by WebGL .

A beautiful garden rendered in created graphics. There's a tree with bright red leaves. A blue sky full of puffy white clouds. Bright green grass, with a path leading by plants and garden sculpture. It's a world created in WebXR.

Safari for visionOS 2 supports the new WebXR transient-pointer input mode. It lets you make the most of natural input on visionOS, and allow your users to interact with a look and a pinch.

We are in a rendered 3d environment, in a garden. We look at a chess board, with a real human hand lifting a rendered chess piece to make the next move in the game. A floating panel has two buttons reading "Leave garden" and "Reset game".

If you want to animate a 3D model of the user’s hands, Safari for visionOS 2 also includes support for WebXR hand tracking . To ensure privacy, permission to allow hand tracking will be requested from users at the start of their WebXR session.

Learn all about WebXR on visionOS 2 by watching Build immersive web experiences with WebXR from WWDC. Learn more about transient-pointer input mode by reading Introducing natural input for WebXR in Apple Vision Pro . And learn all about how to use Safari’s developer tools on Mac to inspect and debug in Apple Vision Pro by reading Try out your website in the spatial web .

Spatial photos and panoramas

One of the amazing experiences you can have on Apple Vision Pro is looking at spatial photos and panoramas. The web is a great place to share these photos with others.

A family blows out candles on a birthday cake in a photo — that's floating in a frame in midair, in a living room. This is a still from the WWDC23 Keynote that introduced Apple Vision Pro. It's an example of how spatial photos work.

When you open the Photos app in visionOS, you see your library of photos. When you tap an image, it appears alone in a floating frame in front of you. Spatial photos appear at just the right height and viewing angle to make it feel like you’ve gone back to a moment in time. A second tap of the UI breaks a spatial photo out of its frame, becoming even more immersive. Similarly, a panorama floats in a frame on first tap. Then on second tap of the UI, it expands to wrap all around you, creating a fully immersive experience.

Now in Safari 18.0 for visionOS 2, you can use the JavaScript Fullscreen API to create a similar experience on the web. You can embed the photo in a web page, and provide the ability to tap. The photo will pop into a floating frame as the Safari window disappears. Then when the user taps on the spatial photo or panorama UI that visionOS provides, the photo will further expand to create a more immersive experience. When they exit the image, the Safari window will return.

Let’s walk through how to support experiencing a spatial photo or panorama on the web using Fullscreen API. First, include the image on your web page using any of the techniques used for years. Here, we can embed a flattened panoramic photo into the web page using simple HTML.

Then using JavaScript, we’ll trigger .requestFullscreen() on tap. Perhaps like this.

You could, of course, create your own UI for the user to tap, rather than making the entire photo the tap target.

Spatial images work just the same, although it’s likely we want to provide fallbacks for browsers that do not support HEIC files . We can do so with the picture element.

Spatial images are stereoscopic, with both a left and right channel. In Safari, when the image is embedded in the web page, the browser will show the left channel. And there’s no need to worry about providing a fallback of any sort for Safari on macOS, iOS, or iPadOS — the stereoscopic HEIC file works great.

This technique will also cause images to go fullscreen in any browser that supports Fullscreen API. Learn more about adding panorama and spatial photos to your websites by watching Optimize for the spatial web from WWDC.

Shaping interaction regions on visionOS

As a web developer, you’re very familiar with how link styling works on the web. For decades you’ve been able to use CSS to style text-decoration , color and more for :link , :hover , :active , and :visited states. You’ve also been able to adjust the size of the invisible tap target through use of padding.

Apple Vision Pro adds a new dimension to how links work — tap targets are visible on visionOS. Anytime a user looks at an interactive element, it’s highlighted to let them know that it can be tapped. And you as a designer or developer can intentionally design how an interaction region looks. You may want to add padding, for instance, or even a rounded corner to the otherwise invisible box.

Now in Safari in visionOS 2 , when you use CSS clip-path to change the shape of tappable area of a link, the visible interaction region will change shape as well. Interactive UI elements built with SVG and cursor: pointer will also be highlighted with the proper shape. Learn more by watching Optimize for the spatial web from WWDC.

Video on visionOS

Safari for visionOS 2 adds support for docking fullscreen videos into the current Environment . Anytime a user is watching a video fullscreen, they can tap the mountain symbol to enter an immersive experience. Turning the Digital Crown adjusts the immersion.

Writing Suggestions

At last year’s WWDC, Apple unveiled inline predictive text on iOS, iPadOS, macOS and more. It helps users input text faster by predicting what they might be typing and finishing the word, phrase or even a whole sentence when the user taps the space bar. Now, WebKit for Safari 18.0 on iOS, iPadOS, visionOS, macOS Sequoia and macOS Sonoma brings inline predictive text to the web.

While inline predictive text makes for a fantastic, personalized user experience, there might be specific situations on the web where it’s better to not have predictions. WebKit for Safari 18.0 on iOS, iPadOS, visionOS, macOS Sequoia and macOS Sonoma gives web developers the opportunity to disable inline predictions through the writingsuggestions attribute. By default, writing suggestions is set to true. You can turn off the capability by including the writingsuggestions="false" attribute on any type of text input field.

WebKit for Safari on iOS 18 adds haptic feedback for <input type=checkbox switch> . This means, now when a user taps a switch control on iPhone, a single tap is felt — just like how toggling a switch feels in Settings app on iOS. Try this demo to see what it’s like.

Date and time inputs

WebKit for Safari 18.0 on macOS improves accessibility support for date and time input field types. Now <input type="date"> , <input type="datetime-local"> , and <input type="time"> elements work properly with VoiceOver.

Usually elements have the labels they need, but sometimes there is no text label for a particular button or UI. In this situation, ARIA can be used to provide an accessible label. The aria-label attribute provides names of labels while aria-roledescription provides the description for the role of an element.

On very rare occasions, you may need to override aria-label or aria-roledescription to provide different names or descriptions specifically for braille. The aria-braillelabel and aria-brailleroledescription attributes provide such an ability. They exist to solve very specific needs, including educational contexts where the site needs to render the specific braille table dot pattern. If you do use braille-related ARIA attributes, be sure to test them using a braille reader. If in doubt, relying on the accessible name from content or aria-label / aria-roledescription is almost always the better user experience . WebKit has supported these ARIA attributes for years.

Now, WebKit for Safari 18.0 adds support for the ariaBrailleLabel and ariaBrailleRoleDescription element reflection properties. These make it possible to get and set the aria-braillelabel and aria-brailleroledescription ARIA attributes on DOM elements directly via JavaScript APIs, rather than by using setAttribute and getAttribute .

WebKit for Safari 18.0 adds support for Unicode 15.1.0 characters in RegExp. Unicode 15.1 added 627 characters, bringing the total of characters to 149,813. Now, these new characters can be used in regular expressions.

WebKit for Safari 18.0 also adds support for the v flag with RegExp.prototype[Symbol.matchAll] . providing more powerful ways to match Unicode characters, as specified in the ECMAScript 2024 standard.

For example, you can now specify to only match on Latin characters, while avoiding matching on Cyrillic script characters.

Or split a string matching on Emojis.

WebKit for Safari 18.0 adds support for URL.parse() , a way to parse URLs which returns null rather than an exception when parsing fails.

WebKit for Safari 18.0 expands Declarative Shadow tree support by adding the shadowRootDelegatesFocus and shadowRootClonable IDL attributes to the <template> element. It also adds the shadowRootSerializable attribute and shadowRootSerializable IDL attribute to the <template> element, enabling those using Declarative Shadow roots to opt into making them serializable. Serializing can be done through the new getHTML() method that has been added at the same time.

WebKit for Safari 18.0 adds support for PopStateEvent ’s hasUAVisualTransition , indicating whether the user agent has a visual transition in place for the fragment navigation.

WebKit for Safari 18.0 adds support for subresource integrity in imported module scripts, which gives cryptographic assurances about the integrity of contents of externally-hosted module scripts.

WebKit for Safari 18.0 adds support for the bytes() method to the Request, Response , Blob , and PushMessageData objects. This replaces the need for web developers to call arrayBuffer() , which can be difficult to use, and wraps the result in a Uint8Array . Calling bytes() is now the recommended way going forward when you need to access the underlying bytes of the data these objects represent.

WebKit for Safari 18.0 adds support for feature detecting text fragments by exposing document.fragmentDirective . Note that the returned object (a FragmentDirective ) doesn’t provide any functionality, but it’s helpful if you need to know if Fragment Directives are supported by the browser.

WebKit for Safari 18.0 adds support for the willReadFrequently context attribute for the getContext() method. It indicates whether or not a lot of read-back operations are planned. It forces the use of a software accelerated 2D or offscreen canvas, instead of hardware accelerated. This can improve performance when calling getImageData() frequently.

WebKit for Safari 18.0 extends 2D canvas support for currentcolor . It can now be used inside color-mix() or Relative Color Syntax. Here currentcolor will default to the computed color property value on the canvas element.

WebKit for Safari 18.0 adds Workers support for both Managed Media Source (MMS) and Media Source Extensions ( MSE ). This can be especially helpful on complex websites that want to ensure continuous and smooth video playback even when other site activity (such as live commenting) causes a very busy main thread. You can see the performance difference in this demo .

WebKit for Safari 18.0 adds support for the WebRTC HEVC RFC 7789 RTP Payload Format. Previously, the WebRTC HEVC used generic packetization instead of RFC 7789 packetization. This payload format provides a new option for improving videoconferencing, video streaming, and delivering high-bitrate movies and TV shows.

WebKit for Safari 18.0 adds support for MediaStreamTrack processing in a dedicated worker. And it adds support for missing WebRTC stats.

WebKit for Safari 18.0 adds support for secure HTTPS for all images, video, and audio by upgrading passive subresource requests in mixed content settings. This means that if some files for a website are served using HTTPS and some are served using HTTP (known as “mixed content”), all images and media will now be auto-upgraded to HTTPS, in adherence with Mixed Content Level 2 .

WebKit for Safari 18.0 adds support for six new WebGL extensions:

  • EXT_texture_mirror_clamp_to_edge
  • WEBGL_render_shared_exponent
  • WEBGL_stencil_texturing
  • EXT_render_snorm
  • OES_sample_variables
  • OES_shader_multisample_interpolation

WebKit for Safari 18.0 adds support for fuzzy search code completion in the Web Inspector’s CSS source editor.

Two years ago at WWDC22, we announced support for passkeys — a groundbreaking industry-standard way to login to websites and app services. Passkeys provide people with an extremely easy user experience, while delivering a profound increase in security. To learn more, watch Meet Passkeys or read Supporting passkeys .

WebKit for Safari 18.0 adds support for three new features as we continue to improve passkeys. First, Safari 18.0 adds support for using mediation=conditional for web authentication credential creation. This allows websites to automatically upgrade existing password-based accounts to use passkeys. Learn more by watching Streamline sign-in with passkey upgrades and credential managers from WWDC.

Second, WebKit for Safari 18.0 adds support for using passkeys across related origins. This lets websites use the same passkey across a limited number of domains which share a credential backend.

And third, WebKit for Safari 18.0 adds support for the WebAuthn prf extension. It allows for retrieving a symmetric key from a passkey to use for the encryption of user data.

Safari 18.0 also adds support for Mobile Device Management of extension enabled state, private browsing state, and website access on managed devices. This means schools and businesses that manage iOS, iPadOS, or macOS devices can now include the configuration of Safari App Extensions, Content Blockers, and Web Extensions in their management.

WebKit for Safari 18.0 adds support for funds transfer via Apple Pay.

While it’s rare to deprecate older technology from the web, there are occasions when it makes sense. We’ve been busy removing -webkit prefixed properties that were never standardized, aging media formats that were never supported in other browsers, and more. This helps align browser engines, improve interoperability, and prevent compatibility problems by reducing the possibility that a website depends on something that’s not a web standard.

WebKit for Safari 18.0 removes support for OffscreenCanvasRenderingContext2D ’s commit() method.

WebKit for Safari 18.0 deprecates support for a number of rarely used -webkit prefixed CSS pseudo-classes and properties — and even one -khtml prefixed property.

  • -webkit-alt and alt properties
  • :-webkit-animating-full-screen-transition pseudo-class
  • :-webkit-full-screen-ancestor pseudo-class
  • :-webkit-full-screen-controls-hidden pseudo-class
  • :-webkit-full-page-media pseudo-class
  • :-webkit-full-screen-document pseudo-class
  • :-khtml-drag pseudo-class

WebKit for Safari 18.0 also deprecates support for the resize: auto rule. Support for the resize property remains, just as it’s been since Safari 4. The values Safari continues to support include : none , both , horizontal , vertical , block , inline , plus the global values. Early versions of CSS Basic User Interface Module Level 3 defined auto , but it was later written out of the web standard.

WebKit for Safari 18.0 also deprecates support for non-standardize WEBKIT_KEYFRAMES_RULE and WEBKIT_KEYFRAME_RULE API in CSSRule .

WebKit for Safari 18.0 removes support for the JPEG2000 image format. Safari was the only browser to ever provide support.

If you’ve been serving JPEG2000 files using best practices, then your site is using the picture element to offer multiple file format options to every browser. Safari 18.0 will simply no longer choose JPEG2000, and instead use a file compressed in JPEG XL, AVIF, WebP, HEIC, JPG/JPEG, PNG, or Gif — choosing the file that’s best for each user. Only one image will be downloaded when you use <picture> , and the browser does all the heavy lifting.

We have noticed that some Content Deliver Networks (CDN) use User Agent sniffing to provide one file to each UA, offering only JPEG2000 images to Safari — especially on iPhone and iPad. If you expect this might be happening with your site, we recommend testing in Safari 18.0 on both macOS Sequoia and iOS or iPadOS 18. If you see problems, contact your SaaS provider or change your image delivery settings to ensure your website provides fallback images using industry best practices.

If you notice a broken site, please file an issue at webcompat.com .

WebKit for Safari 18.0 removes [[VarNames]] from the global object to reflect changes in the web standard, a change that now allows this code to work:

WebKit for Safari 18.0 removes support for non-standard VTTRegion.prototype.track .

WebKit for Safari 18.0 removes the last bits of support for AppCache.

When AppCache first appeared in 2009, in Safari 4, it held a lot of promise as a tool for caching web pages for use offline. It was imagined as “HTML5 Application Cache” back when HTML itself was being further expanded to handle more use cases for web applications. A developer could create a simple cache manifest file with a list of files to be cached. Its simplicity looked elegant, but there was no mechanism for cache busting, and that made both developing a site and evolving the site over time quite frustrating. AppCache also had security challenges. So new web standards were created to replace it. Today, developers use Service Workers and Cache Storage instead.

WebKit deprecated AppCache with a warning to the Console in Safari 11.0. Then in 2021, we removed support for AppCache from Safari 15.0, with a few exceptions for third-party users of WKWebView . Now we are removing those exceptions. This change to WebKit will only affect the rare web content loaded in older third-party apps that have JavaScript code which relies on the existence of AppCache related interfaces.

WebKit for Safari 18.0 removes the SVGAnimateColorElement interface, as well as the non-standard getTransformToElement from SVGGraphicsElement .

WebKit for Safari 18.0 removes support for four non-standard Web APIs:

  • KeyboardEvent.altGraphKey
  • AES-CFB support from WebCrypto
  • KeyboardEvent.prototype.keyLocation
  • HashChangeEvent ’s non-standard initHashChangeEvent() method

Deprecated some legacy WebKit notification names including:

  • WebViewDidBeginEditingNotification
  • WebViewDidChangeNotification
  • WebViewDidEndEditingNotification
  • WebViewDidChangeTypingStyleNotification
  • WebViewDidChangeSelectionNotification

In addition to all the new features, WebKit for Safari 18.0 includes work to polish existing features.

Accessibility

  • Fixed role assignment for <header> inside <main> and sectioning elements.
  • Fixed range input not firing an input event when incremented or decremented via accessibility APIs.
  • Fixed setting aria-hidden on a slot not hiding the slot’s assigned nodes.
  • Fixed VoiceOver to read hidden associated labels.
  • Fixed comboboxes to expose their linked objects correctly.
  • Fixed VoiceOver support for aria-activedescendant on macOS.
  • Fixed time input accessibility by adding labels to subfields.
  • Fixed aria-hidden=true to be ignored on the <body> and <html> elements.
  • Fixed datetime values being exposed to assistive technologies in the wrong timezone.
  • Fixed wrong datetime value being exposed to assistive technologies for datetime-local inputs.
  • Fixed ignored CSS content property replacement text when it is an empty string.
  • Fixed the computed role for these elements: dd , details , dt , em , hgroup , option , s , and strong .
  • Fixed hidden elements targeted by aria-labelledby to expose their entire subtree text, not just their direct child text.
  • Fixed accessible name computation for elements with visibility: visible inside a container with visibility: hidden .
  • Fixed updating table accessibility text when its caption dynamically changes.
  • Fixed updating aria-describedby text after the targeted element changes its subtree.
  • Fixed the transition property to produce the shortest serialization.
  • Fixed the animation property to produce the shortest serialization.
  • Fixed arbitrary 8 digit limit on a line item’s total amount.

Authentication

  • Fixed navigator.credentials.create() rejects with “NotAllowedError: Operation Failed” after a conditional UI request is aborted.
  • Fixed setting the cancel flag once the cancel completes regardless of a subsequent request occurring.
  • Fixed drawImage(detachedOffscreenCanvas) to throw an exception.
  • Fixed OffscreenCanvas failing to render to the placeholder with nested workers.
  • Fixed losing the contents layer of the placeholder canvas of OffscreenCanvas when switching off the tab.
  • Fixed drawImage to not alter the input source or the destination rectangles.
  • Fixed toggling the visibility on a canvas parent undoing the effect of clearRect() .
  • Fixed the Canvas drawImage() API to throw an exception when the image is in broken state.
  • Fixed a detached OffscreenCanvas to not transfer an ImageBuffer.
  • Fixed treating the lack of an explicit “SameSite” attribute as “SameSite=Lax”.
  • Fixed setting white-space to a non-default value dynamically on a whitespace or a new line.
  • Fixed custom counter styles disclosure-open and disclosure-closed to point to the correct direction in right-to-left.
  • Fixed backface-visibility to create a stacking context and containing block.
  • Fixed getComputedStyle() to work with functional pseudo-elements like ::highlight() .
  • Fixed: Aliased :-webkit-full-screen pseudo-class to :fullscreen .
  • Fixed: Aliased :-webkit-any-link to :any-link and :matches() to :is() .
  • Fixed getComputedStyle() pseudo-element parsing to support the full range of CSS syntax.
  • Fixed @supports to correctly handle support for some -webkit prefixed pseudo-elements that were incorrectly treated as unsupported.
  • Fixed updating media-query sensitive meta tags after style changes.
  • Fixed changing color scheme to update gradients with system colors or light-dark() .
  • Fixed incorrect inline element size when using font-variant-caps: all-small-caps with font-synthesis .
  • Fixed :empty selector to work with animations.
  • Fixed preserving whitespace when serializing custom properties.
  • Fixed updating style correctly for non-inherited custom property mutations.
  • Fixed element removed by parent to end up losing the last remembered size.
  • Fixed an incorrect difference between implicit and explicit initial values for custom properties.
  • Fixed the contrast of Menu and MenuText system colors.
  • Fixed keeping the shorthand value for CSS gap as-is in serialized and computed values.
  • Fixed the style adjuster for @starting-style incorrectly invoking with a null element.
  • Fixed excluding -apple-pay-button from applying to any element that supports appearance: auto and is not a button.
  • Fixed missing color interpretation methods added to CSS color specifications.
  • Fixed hsl() and hsla() implementation to match the latest spec changes.
  • Fixed the implementation of rgb() and rgba() to match the latest spec.
  • Fixed the hwb() implementation to match the latest spec.
  • Fixed the remaining color types to be synced with the latest spec changes.
  • Fixed carrying analogous components forward when interpolating colors.
  • Fixed applying the fill layer pattern for mask-mode .
  • Fixed backdrop-filter: blur to render for elements not present when the page is loaded.
  • Fixed: Improved large Grid performance.
  • Fixed some CSS properties causing quotes to be reset.
  • Fixed an issue where input method editing would sporadically drop the composition range.
  • Fixed dictation UI no longer showing up when beginning dictation after focusing an empty text field. (FB14277296)
  • Fixed displayed datalist dropdown to sync its options elements after a DOM update.
  • Fixed input elements to use the [value] as the first fallback step base.
  • Fixed <select multiple> scrollbars to match the used color scheme.
  • Fixed updating the input value when selecting an <option> from a <datalist> element. (FB13688998)
  • Fixed the value attribute not getting displayed in an input element with type="email" and the multiple attribute.
  • Fixed the iOS animation for <input type=checkbox switch> .
  • Fixed form controls drawing with an active appearance when the window is inactive.
  • Fixed constructed FormData object to not include entries for the image button submitter by default.
  • Fixed the properties of History to throw a SecurityError when not in a fully active Document.
  • Fixed “about:blank” document.referrer initialization.
  • Fixed parsing a self-closing SVG script element. It now successfully executes.
  • Fixed RegExp.prototype.@@split to update the following legacy RegExp static properties: RegExp.input , RegExp.lastMatch , RegExp.lastParen , RegExp.leftContext , RegExp.rightContext , and RegExp.$1, ... RegExp.$9 .
  • Fixed String.prototype.replace to not take the fast path if the pattern is RegExp Object and the lastIndex is not numeric.
  • Fixed spec compliance for Async / Await, Generators, Async Functions, and Async Generators.
  • Fixed async functions and generators to properly handle promises with throwing “constructor” getter.
  • Fixed return in async generators to correctly await its value.
  • Fixed Symbol.species getters to not share a single JS Function.
  • Fixed throwing a RangeError if Set methods are called on an object with negative size property.
  • Fixed eval() function from another realm to not cause a direct eval call.
  • Fixed eval() call with ...spread syntaxt to be a direct call.
  • Fixed try/catch to not intercept errors originated in [[Construct]] of derived class.
  • direct eval() in a default value expression inside a rest parameter creates a variable in the environment of the function rather than the separate one of the parameters;
  • a ReferenceError is thrown when accessing a binding, which is defined inside rest parameter, in eval() , or a closure created in a default value expression of a preceding parameter, but only if there is a var binding by the same name;
  • a closure, created in the default value expression inside a rest parameter, is created in a different VariableEnvironment of the function than its counterparts in preceding parameters which causes the incorrect environment to be consulted when querying or modifying parameter names that are “shadowed” by var bindings.
  • Fixed TypedArray sorting methods to have a special-case for camparator returning false .
  • Fixed programming style for bitwise and in setExpectionPorts.
  • Fixed emitReturn() to load this value from arrow function lexical environment prior to the TDZ check.
  • Fixed NFKC normalization to work with Latin-1 characters.
  • Fixed parsing of private names with Unicode start characters.
  • Fixed instanceof to not get RHS prototype when LHS is primitive.
  • Fixed bracket update expression to resolve property key at most once.
  • Fixed bracket compound assignement to resolve the property key at most once.
  • Fixed Object.groupBy and Map.groupBy to work for non-objects.
  • Fixed Array.fromAsync to not call the Array constructor twice.
  • Fixed inconsistent output of Function.prototype.toString for accessor properties.
  • Fixed Set#symmetricDifference to call this.has in each iteration.
  • Fixed logical assignment expressions to throw a syntax error when the left side of the assignment is a function call.
  • Fixed throwing a syntax error for nested duplicate-named capturing groups in RegEx.
  • Fixed ArrayBuffer and SharedArrayBuffer constructor to check length before creating an instance.
  • Fixed Intl implementation to ensure canonicalizing “GMT” to “UTC” based on a spec update.
  • Fixed RegEx lookbehinds differing from v8.
  • Fixed fractionalDigits of Intl.DurationFormat to be treated as at most 9 digits if it is omitted.
  • Fixed optimized TypedArrays giving incorrect results.
  • Fixed Intl.DurationFormat for numeric and 2-digit .
  • Fixed navigator.cookieEnabled to return false when cookies are blocked.
  • Fixed MediaSession to determine the best size artwork to use when the sizes metadata attribute is provided. (FB9409169)
  • Fixed video sound coming from another window after changing tabs in the Tab Bar in visionOS.
  • Fixed playback for MSE videos on some sites.
  • Fixed allowing a video’s currentTime to be further than the gap’s start time.
  • Fixed broken audio playback for a WebM file with a Vorbis track.
  • Fixed sampleRate and numberOfChannels to be required and non-zero in a valid AudioEncoderConfig.
  • Fixed media elements appending the same media segment twice.
  • Fixed an issue where Safari audio may be emitted from the wrong window in visionOS.
  • Fixedrejecting valid NPT strings if ‘hours’ is defined using 1 digit.
  • Fixed picture-in-picture when hiding the <video> element while in Viewer.
  • Fixed the return button not working after the video is paused and played in picture-in-picture.
  • Fixed upgrading inactive or passive subresource requests and fetches in would-be mixed security contexts to match standards.
  • Fixed incorrect Sec-Fetch-Site value for navigation of a nested document.
  • Fixed loading WebArchives with a non-persistent datastore.
  • Fixed Timing-Allow-Origin to not apply to an HTTP 302 response.
  • Fixed print buttons with a print action implementation.
  • Fixed Open in Preview for a PDF with a space in its name.
  • Fixed “Open with Preview” context menu item to work with locked PDF documents.
  • Fixed Greek uppercase transforms failing for some characters.
  • Fixed resizing a <textarea> element with 1rem padding.
  • Fixed the color correctness of the color matrix filter.
  • Fixed backdrop-filter to apply to the border area of an element with a border-radius .
  • Fixed intrinsic inline size calculators to account for whitespace before an empty child with nonzero margins.
  • Fixed overlapping elements with flex box when height: 100% is applied on nested content.
  • Fixed incorrect grid item positioning with out-of-flow sibling.
  • Fixed break-word with a float discarding text.
  • Fixed min-content calculation for unstyled only-child inlines elements.
  • Fixed ellipsis rendering multiple times when position: relative and top are used.
  • Fixed a bug for inline elements inserted in reverse order after a block in a continuation.
  • Fixed the flash of a page background-colored bar in the footer when the window is resized.
  • Fixed garbled bold text caused by glyph lookup using the wrong font’s glyph IDs when multiple installed fonts have the same name. (FB13909556)
  • Fixed selecting Japanese text annotated with ruby in a vertical-rl writing mode table.
  • Fixed support for border, padding, and margin on mfrac and mspace elements in MathML.
  • Fixed the cursor not updating as content scrolls under it on some pages.
  • Fixed stripping the scroll-to-text fragment from the URL to prevent exposing the fragment to the page.
  • Fixed CORS bypass on private localhost domain using 0.0.0.0 host and mode “no-cors”.
  • Fixed blocking cross-origin redirect downloads in an iframe.
  • Fixed blocked cross-origin redirect downloads to attempt rendering the page instead.
  • Fixed the SVG parser to interpret “form feed” as white space.
  • Fixed error handling for invalid filter primitive references.
  • Fixed displaying an SVG element inside a <switch> element.
  • Fixed SVG title to have display: none as the default UA style rule.
  • Fixed the UA stylesheet for links in SVGs to apply cursor: pointer matching standards.
  • Fixed returning the initial value for the SVG gradient stop-color if it is not rendered in the page.
  • Fixed the SVG marker segment calculations if the marker path consists of sub-paths.
  • Fixed SVGLength to sync with the WebIDL specification.
  • Fixed disclosure counter styles to consider writing-mode .

Web Animations

  • Fixed percentage transform animations when width and height are animated.
  • Fixed updating an animation when changing the value of a transform property while that property is animated with an implicit keyframe.
  • Fixed display transition to none .
  • Fixed cssText setter to change the style attribute when the serialization differs. (FB5535475)
  • Fixed history.pushState() and history.replaceState() to ignore the title argument.
  • Fixed URL text fragment directives not fully stripped from JavaScript.
  • Fixed showPicker() method to trigger suggestions from a datalist .
  • Fixed lang attribute in no namespace to only apply to HTML and SVG elements.
  • Fixed unnecessarily unsetting the iframe fullscreen flag.
  • Fixed DOM Range to correctly account for CDATASection nodes.
  • Fixed getGamepads() to no longer trigger an insecure contexts warning.
  • Fixed inserting a <picture> element displaying the same image twice.
  • Fixed throwing exceptions in navigation methods if in a detached state.
  • Fixed a minor issue in URL’s host setter.
  • Fixed cloning of ShadowRoot nodes following a DOM Standard clarification.
  • Fixed GeolocationCoordinates to expose a toJSON() method.
  • Fixed IntersectionObserver notifications that sometimes fail to fire.
  • Fixed GeolocationPosition to expose a toJSON() method.
  • Fixed setting CustomEvent.target when dispatching an event.
  • Fixed navigator.language only returning the system language in iOS 17.4.
  • Fixed: Removed presentational hints from the width attribute for <hr> .
  • Fixed an issue when inserting writing suggestions into an editable display: grid container.
  • Fixed the warning message for window.styleMedia .
  • Fixed resolving www. sub-domain for Associated Domains for all web apps.

Web Assembly

  • Fixed initialization of portable reference typed globals.

Web Extensions

  • Fixed getting an empty key from storage. (FB11427769)
  • Fixed Service Workers not appearing in the Develop menu or remote Web Inspector menu. (130712941)
  • Fixed web extensions unable to start due to an issue parsing declarativeNetRequest rules. (FB14145801)
  • Fixed font sizes in the Audits tab.
  • Fixed expanded sections of Storage to not collapse.
  • Fixed Web Inspector to show nested workers.
  • Fixed CSS font property values marked !important not getting overridden when using the interactive editing controls.
  • Fixed an issue where the Web Inspector viewport might appear cut off.
  • Fixed runtimes to be aligned in the Audit tab.
  • Fixed remembering the message type selection in the Console tab.
  • Fixed autocomplete for the text-indent property suggesting prefixed properties instead of each-line or hanging .
  • Fixed background autocompletion suggestion to include repeating-conic-gradient .
  • Fixed the list of breakpoints in the Sources tab disappearing when Web Inspector is reloaded.
  • Fixed console clearing unexpectedly when Web Inspector reopens.
  • Fixed console code completion to be case-insensitive.
  • Fixed overflow: scroll elements to scroll as expected when highlighting an element from the DOM tree.
  • Fixed showing additional Safari tabs from an iOS device in the Develop menu.
  • Fixed Console and code editor completion not auto-scrolling the suggestion into view.
  • Fixed search in the DOM tree view unexpectedly chaning the text display.
  • Fixed clicking the “goto” arrow for computed CSS when “show independent Styles sidebar” is disabled.
  • Fixed inspectable tabs from Safari in the visionOS Simulator don’t appear in Developer menu on the host macOS.
  • Fixed Accessibility inspector for switch controls to report “State: on/off” instead of “Checked: true/false”.
  • Fixed Gamepad API in WKWebView.
  • Fixed repainting HTML elements when their width or height change in legacy WebView.
  • Fixed retrieving titles containing multibyte characters.
  • Fixed RTCEncodedVideoFrame and RTCEncodedAudioFrame to match the WebIDL specification.
  • Fixed VideoTrackGenerator writer to close when its generator track (and all its clones) are stopped.
  • Fixed WebRTC AV1 HW decoding on iPhone 15 Pro.
  • Fixed black stripes with screen sharing windows.
  • Fixed black stripes with getDisplayMedia captured windows when the window is resized.

Safari 18.0 is available on iOS 18 , iPadOS 18 , macOS Sequoia , macOS Sonoma, macOS Ventura, and in visionOS 2 .

If you are running macOS Sonoma or macOS Ventura, you can update Safari by itself, without updating macOS. Go to ïŁż > System Settings > General > Software Update and click “More info
” under Updates Available.

To get the latest version of Safari on iPhone, iPad or Apple Vision Pro, go to Settings > General > Software Update, and tap to update.

We love hearing from you. To share your thoughts on Safari 18.0, find us on Mastodon at @[email protected] and @[email protected] . Or send a reply on X to @webkit . You can also follow WebKit on LinkedIn . If you run into any issues, we welcome your feedback on Safari UI (learn more about filing Feedback ), or your WebKit bug report about web technologies or Web Inspector. If you notice a website that seems broken in Safari, but not in other browsers, please file a report at webcompat.com . Filing issues really does make a difference.

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

You can also find this information in the Safari 18.0 release notes .

1. iPhone Mirroring is available on Mac computers with Apple silicon and Intel-based Mac computers with a T2 Security Chip. Requires that your iPhone and Mac are signed in with the same Apple ID using two-factor authentication, your iPhone and Mac are near each other and have Bluetooth and Wi-Fi turned on, and your Mac is not using AirPlay or Sidecar. iPhone Mirroring is not available in all regions.

  • Español – AmĂ©rica Latina
  • PortuguĂȘs – Brasil
  • TiĂȘ́ng ViĂȘÌŁt
  • Chrome Extensions

Content scripts

Content scripts are files that run in the context of web pages. Using the standard Document Object Model (DOM), they are able to read details of the web pages the browser visits, make changes to them, and pass information to their parent extension.

Understand content script capabilities

Content scripts can access the following extension APIs directly:

  • runtime.connect()
  • runtime.getManifest()
  • runtime.getURL()
  • runtime.onConnect
  • runtime.onMessage
  • runtime.sendMessage()

Content scripts are unable to access other APIs directly. But they can access them indirectly by exchanging messages with other parts of your extension.

You can also access other files in your extension from a content script, using APIs like fetch() . To do this, you need to declare them as web-accessible resources . Note that this also exposes the resources to any first-party or third-party scripts running on the same site.

Work in isolated worlds

Content scripts live in an isolated world, allowing a content script to make changes to its JavaScript environment without conflicting with the page or other extensions' content scripts.

An extension may run in a web page with code similar to the following example.

webPage.html

That extension could inject the following content script using one of the techniques outlined in the Inject scripts section.

content-script.js

With this change, both alerts appear in sequence when the button is clicked.

Inject scripts

Content scripts can be declared statically , declared dynamically , or programmatically injected .

Inject with static declarations

Use static content script declarations in manifest.json for scripts that should be automatically run on a well known set of pages.

Statically declared scripts are registered in the manifest under the "content_scripts" key. They can include JavaScript files, CSS files, or both. All auto-run content scripts must specify match patterns .

manifest.json

Inject with dynamic declarations

Dynamic content scripts are useful when the match patterns for content scripts are not well known or when content scripts shouldn't always be injected on known hosts.

Introduced in Chrome 96, dynamic declarations are similar to static declarations , but the content script object is registered with Chrome using methods in the chrome.scripting namespace rather than in manifest.json . The Scripting API also allows extension developers to:

  • Register content scripts.
  • Get a list of registered content scripts.
  • Update the list of registered content scripts.
  • Remove registered content scripts.

Like static declarations, dynamic declarations can include JavaScript files, CSS files, or both.

service-worker.js

Inject programmatically

Use programmatic injection for content scripts that need to run in response to events or on specific occasions.

To inject a content script programmatically, your extension needs host permissions for the page it's trying to inject scripts into. Host permissions can either be granted by requesting them as part of your extension's manifest or temporarily using "activeTab" .

The following is a different versions of an activeTab-based extension.

manifest.json:

Content scripts can be injected as files.

service-worker.js:

Or, a function body can be injected and executed as a content script.

Be aware that the injected function is a copy of the function referenced in the chrome.scripting.executeScript() call, not the original function itself. As a result, the function's body must be self contained; references to variables outside of the function will cause the content script to throw a ReferenceError .

When injecting as a function, you can also pass arguments to the function.

Exclude matches and globs

To customize specified page matching, include the following fields in a declarative registration.

The content script will be injected into a page if both of the following are true:

  • Its URL matches any matches pattern and any include_globs pattern.
  • The URL doesn't also match an exclude_matches or exclude_globs pattern. Because the matches property is required, exclude_matches , include_globs , and exclude_globs can only be used to limit which pages will be affected.

The following extension injects the content script into https://www.nytimes.com/health but not into https://www.nytimes.com/business .

Glob properties follow a different, more flexible syntax than match patterns . Acceptable glob strings are URLs that may contain "wildcard" asterisks and question marks. The asterisk ( * ) matches any string of any length, including the empty string, while the question mark ( ? ) matches any single character.

For example, the glob https://???.example.com/foo/\* matches any of the following:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

However, it does not match the following:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

This extension injects the content script into https://www.nytimes.com/arts/index.html and https://www.nytimes.com/jobs/index.htm* , but not into https://www.nytimes.com/sports/index.html :

This extension injects the content script into https://history.nytimes.com and https://.nytimes.com/history , but not into https://science.nytimes.com or https://www.nytimes.com/science :

One, all, or some of these can be included to achieve the correct scope.

The run_at field controls when JavaScript files are injected into the web page. The preferred and default value is "document_idle" . See the RunAt type for other possible values.

Specify frames

The "all_frames" field allows the extension to specify if JavaScript and CSS files should be injected into all frames matching the specified URL requirements or only into the topmost frame in a tab.

Inject in to related frames

Extensions may want to run scripts in frames that are related to a matching frame, but don't themselves match. A common scenario when this is the case is for frames with URLs that were created by a matching frame, but whose URLs don't themselves match the script's specified patterns.

This is the case when an extension wants to inject in frames with URLs that have about: , data: , blob: , and filesystem: schemes. In these cases, the URL won't match the content script's pattern (and, in the case of about: and data: , don't even include the parent URL or origin in the URL at all, as in about:blank or data:text/html,<html>Hello, World!</html> ). However, these frames can still be associated with the creating frame.

To inject into these frames, extensions can specify the "match_origin_as_fallback" property on a content script specification in the manifest.

When specified and set to true , Chrome will look at the origin of the initiator of the frame to determine whether the frame matches, rather than at the URL of the frame itself. Note that this might also be different than the target frame's origin (e.g., data: URLs have a null origin).

The initiator of the frame is the frame that created or navigated the target frame. While this is commonly the direct parent or opener, it may not be (as in the case of a frame navigating an iframe within an iframe).

Because this compares the origin of the initiator frame, the initiator frame could be on at any path from that origin. To make this implication clear, Chrome requires any content scripts specified with "match_origin_as_fallback" set to true to also specify a path of * .

When both "match_origin_as_fallback" and "match_about_blank" are specified, "match_origin_as_fallback" takes priority.

Communication with the embedding page

Although the execution environments of content scripts and the pages that host them are isolated from each other, they share access to the page's DOM. If the page wishes to communicate with the content script, or with the extension through the content script, it must do so through the shared DOM.

An example can be accomplished using window.postMessage() :

The non-extension page, example.html, posts messages to itself. This message is intercepted and inspected by the content script and then posted to the extension process. In this way, the page establishes a line of communication to the extension process. The reverse is possible through similar means.

Access extension files

To access an extension file from a content script, you can call chrome.runtime.getURL() to get the absolute URL of your extension asset as shown in the following example ( content.js ):

To use fonts or images in a CSS file, you can use @@extension_id to construct a URL as shown in the following example ( content.css ):

content.css

All assets must be declared as web accessible resources in the manifest.json file:

Stay secure

While isolated worlds provide a layer of protection, using content scripts can create vulnerabilities in an extension and the web page. If the content script receives content from a separate website, such as by calling fetch() , be careful to filter content against cross-site scripting attacks before injecting it. Only communicate over HTTPS in order to avoid "man-in-the-middle" attacks.

Be sure to filter for malicious web pages. For example, the following patterns are dangerous, and disallowed in Manifest V3:

Instead, prefer safer APIs that don't run scripts:

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License , and code samples are licensed under the Apache 2.0 License . For details, see the Google Developers Site Policies . Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2012-09-17 UTC.

IMAGES

  1. Troubleshooting your Safari web extension

    safari web extension content script

  2. Convert a Chrome Extension to Safari Web Extension

    safari web extension content script

  3. Creating a Safari web extension

    safari web extension content script

  4. Safari 12 user script and Plugins and Extensions solution

    safari web extension content script

  5. The four types of Safari extension

    safari web extension content script

  6. Convert a Chrome Extension to Safari Web Extension

    safari web extension content script

VIDEO

  1. Safari is AI Now!

  2. Safari Shortcuts in Mac

  3. UberFrame Beta Preview

  4. How to Block Websites in Safari on Your Mac (2024 Guide)

  5. Javascript :How do I read the local storage of my Safari Web Extension using Selenium in Python?

  6. How To Enable Safari Experimental Features On Iphone (Tutorial)

COMMENTS

  1. Using content script and style sheet keys

    Content scripts and style sheets have similar functionality. Safari injects content scripts (as .js files) and style sheets (as .css files) into webpages to customize web content. Injected scripts and styles have the same access privileges as the scripts and styles that execute from the webpage's host. The scripts can also send messages to ...

  2. Messaging between the app and JavaScript in a Safari web extension

    To enable sending messages from JavaScript to the native app extension, add nativeMessaging to the list of permissions in the manifest.json file. From a script running in the browser or Mac web app, use browser.runtime.sendNativeMessage to send a message to the native app extension: Safari ignores the application.id parameter and only sends the ...

  3. How does a webpage send a message to a Safari Web Extension?

    1. How does javascript code on a webpage trigger the sending of a message to a Safari Web Extension? It appears browser.runtime.connectNative() will allow a background script to communicate with the native app. Most specifically, stateful data can be sent over a runtime.Port.

  4. Safari Web Extension does not load content script on iOS 15

    The content scripts specified in the manifest of an extension should be loaded each time a user visits a web page. However, in the iOS 15 simulator, the content script is only loaded the first time that the extension is enabled -- either when you grant permission for the site, or if you turn the extension off and back on while on a site.

  5. How to communicate between Content Script, Popup, and Background in

    Message passing between different parts of a browser extension is the most confusing part when starting with browser extension development. This post is about how I generally structure my web extensions code to communicate between Content Script, Background, and popups (Browser Action). These are the pieces we will use.

  6. Injecting a script into a webpage

    After specifying website access, add the script to your Safari app extension. Add the script files to your extension's Xcode target. Add an SFSafari Content Script key to the NSExtension element in your extension's Info.plist file. The value for this key is an array of dictionaries. For each script file, add a dictionary to the array.

  7. Content Script and SafariWebExtensionHandler communication

    In Safari App Extension, I could communicate between my content script and extension handler suing the following: Script to Handler. JavaScript. safari.extension.dispatchMessage("messageName", { "text": "Message to Handler" }); Swift. override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {

  8. Creating a Safari web extension

    The project includes the Safari web extension as a macOS or iOS app extension that users can download from the App Store and install in Safari. The Xcode project includes default web extension files, such as the manifest, script files for a content page and background page, files for the toolbar pop-up menu, and folders for images and ...

  9. quoid/userscripts: An open-source userscript manager for Safari

    iOS (iPadOS) After installing the iOS App, you need two main steps to make the extension work: Open the App and set a directory (For saving and loading userscripts) After Userscripts for ios v1.5.0, a local default directory will be set automatically. In earlier versions please click the Set Userscripts Directory button and select the directory.

  10. A minimal-reproducable example of a Safari Web Extension bug I've run

    Open Safari on the device and enable the extension; Open a new tab and navigate to https://www.google.com; From your Mac, open the Safari Web Inspector and navigate to the extension's background page; You will see messages being sent from the content script to the background script every 1 second

  11. Content scripts

    Content scripts. A content script is a part of your extension that runs in the context of a web page (as opposed to background scripts that are part of the extension, or scripts that are part of the website itself, such as those loaded using the <script> element). Background scripts can access all the WebExtension JavaScript APIs, but they can ...

  12. WebKit Features in Safari 18.0

    Now users can personalize web apps on Mac with Safari Web Extensions and Content Blockers. Navigate to the web app's Settings menu to access all the installed Content Blockers and Web Extensions. Any enabled in Safari will be on by default in the web app. Each web app is uniquely customizable, just like Safari profiles. CSS View Transitions

  13. Passing messages between Safari app extensions and injected scripts

    The safari object is an instance of the SafariAppExtensionNamespace class. It provides details about your app extension and support for passing messages between your injected script and the app extension. This table describes your app extension's key properties: A proxy for the app extension. Use it to retrieve information about your app ...

  14. Safari web extensions

    You implement Safari web extensions as macOS or iOS app extensions to provide a safe and secure distribution and usage model. You can distribute a Safari web extension with a Mac app, an iOS app, or a Mac app created using Mac Catalyst. You must use Xcode to package your extensions for testing in Safari, and you must be a member of the Apple ...

  15. Content scripts

    Content scripts are unable to access other APIs directly. But they can access them indirectly by exchanging messages with other parts of your extension. You can also access other files in your extension from a content script, using APIs like fetch (). To do this, you need to declare them as web-accessible resources.

  16. addContentScript

    Adds a content script from a string. Discussion. If run At End is true, the script is run as soon as the page has completely finished loading.Otherwise, it is run as soon as the DOM is ready, before loading subresources such as images and the contents of frames, which allows you to block resources from being loaded.

  17. Safari App Extension content script not loaded on Safari error page

    When running a Safari App Extension, if Safari shows "Failed to open page", your content script isn't loaded. ... Chrome extension content script won't load script from web accessible resources. 0. Google Chrome extension's content script not working on few pages. 1. Load content_script.js when ERR_NAME_NOT_RESOLVED. 0.

  18. How to add Content Security Policy to Safari extension

    Refused to execute inline script because it violates the following Content Security Policy directive: In chrome by adding the content security policy in my manifest.json file i could get away with it. How can i do it for safari extension. Any help or clues are mostly appreciated