I've been unable to successfully get a webpage to send a message to a Safari web extension, no matter what I try doing.
I've added the following to my manifest.json file, and it's running manifest v3
{
"externally_connectable": {
"matches": [ "*://mywebsite.com/*", "*://localhost:3000/*" ]
}
}
My web page executes the following code snippet. I've tried this both while running my site locally (on localhost) and pushed to production.
let safariExtensionId = "co.companyname.productname.Extension (ABCD1234)"
browser.runtime.sendMessage(safariExtensionId, { greeting: "hello"},
function(response) {
console.log("Received response from background page");
console.log(response.farewell);
}
);
In the Safari web extension's background.js file, I've added the following onMessageExternal listener:
browser.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
console.log("Received message from the sender.");
console.log(message.greeting);
sendResponse({ farewell: "Goodbye!" });
});
This is directly copied from the instructions in this WWDC video:
https://developer.apple.com/documentation/safariservices/messaging-between-a-webpage-and-your-safari-web-extension
It's also extremely difficult to debug what's happening since the extensions service working frequently does not appear in the Web Extension Background Content menu
Is there something I'm doing wrong, or a bug I'm not aware of?
General
RSS for tagExplore the integration of web technologies within your app. Discuss building web-based apps, leveraging Safari functionalities, and integrating with web services.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Having the app localised and configured to Dutch from Belgium (nl_BE), I open a url with WKWebView. The website locale detects Dutch from Netherlands (nl_NL)
Since Safari requires a macOS app as a container for Web Extensions, is there a way to establish native messaging directly from SafariWebExtensionHandler using stdin/stdout? Or does Safari enforce a different communication mechanism?
I’d like to keep the same approach as other browsers.
Any guidance on making this work would be appreciated!
<script src="https://js.braintreegateway.com/web/3.92.0/js/client.min.js"></script>
I’ve been working on a personal iOS project for fun — essentially a YouTube music player, learning how background media playback works in native iOS apps.
After seeing that Musi (a famous music streaming app) can play YouTube audio in the background with the screen off — I got really curious. I’ve been trying to replicate that basic background audio functionality for YouTube embeds using WKWebView. I've spent a crazy amount of time (probably 20 hours) trying to figure this out but have achieved no success.
Here’s what I’ve tried so far:
-Embedding a YouTube video in a WKWebView
-Activating AVAudioSession with .playback and setting .setActive(true)
-Adding the UIBackgroundModes key with audio in Info.plist
-Adding the NSAppTransportSecurity key to allow arbitrary loads
--Testing on a real device (iPhone 14, iOS 18.1 target)--
What happens:
Audio plays fine in the foreground.
If I exit the app and go to the lock screen quickly enough (less than 3 seconds) after pressing play, I can resume playback briefly from the lock screen — but it doesn’t automatically continue like in Musi and other apps like it.
Most of the time, the audio stops when the app is backgrounded.
I get this error consistently in the logs:
Error acquiring assertion: <Error Domain=RBSServiceErrorDomain Code=1 "(originator doesn't have entitlement com.apple.runningboard.assertions.webkit AND originator doesn't have entitlement com.apple.multitasking.systemappassertions)"
It seems like the app lacks some specific entitlements related to WebKit media playback. I don’t have AppDelegate/SceneDelegate (using SwiftUI), but can add if needed.
I’m super curious how music streaming apps using youtube as a source get around this — are they doing something different under the hood? A custom player? A SafariViewController trick? Is there a specific way to configure WKWebView to keep playing in the background, or is this a known limitation?
Would really appreciate any insight from folks who’ve explored this before or know how apps like Musi pulled it off.
Thanks in advance!
On iOS 18, when setting the src attribute of an tag to a custom scheme (e.g., myapp://image.png) or an HTTP URL (http://example.com/image.png), if crossorigin="anonymous" is applied, the image fails to load. Additionally, images affected by this issue cannot be drawn to a , as the browser treats them as tainted and blocks access to their pixel data.
This issue did not occur in previous iOS versions and seems to be a regression in iOS 18.
Steps to Reproduce:
Open an HTTPS-hosted H5 page in Safari on iOS 18.
Add an tag with crossorigin="anonymous" and set src to either:
A custom scheme:
<img src="myapp://image.png" crossorigin="anonymous">
An HTTP URL (even from the same origin):
<img src="http://example.com/image.png" crossorigin="anonymous">
Observe that the image does not load.
Attempt to draw the image onto a and retrieve its data:
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "http://example.com/image.png"; // or "myapp://image.png"
img.onload = () => {
ctx.drawImage(img, 0, 0);
try {
console.log(canvas.toDataURL()); // Expect base64 image data
} catch (error) {
console.error("Canvas is tainted:", error);
}
};
Notice that the image is blocked, and any attempt to access pixel data results in a CORS error.
Expected Behavior:
* The image should be displayed if it is accessible under normal CORS rules.
* The API should allow access to the image data unless explicitly blocked by the server’s CORS policy.
Actual Behavior:
The image fails to load when crossorigin="anonymous" is applied.
The API does not allow access to the image data, treating it as tainted.
Removing crossorigin="anonymous" allows the image to display in some cases, but this is not a viable workaround when CORS enforcement is required.
Regression:
Works correctly on: iOS 17 and earlier
Broken on: iOS 18
Environment:
Device: iPhone/iPad
iOS Version: 18.0+
Browser: Safari
Suggested Fix:
Apple should investigate this regression and allow custom schemes and HTTP images to be correctly handled under CORS policies when crossorigin="anonymous" is set. If the source allows cross-origin requests, Safari should not block the image or its use in .
Our service includes the Apple web login feature to support "Sign in with Apple" on iOS 12.
However, at some point, an error started occurring on the Apple login page in iOS 12, preventing users from proceeding further.
Upon checking the Web Inspector console, we found the following error:
Failed to load resource: the server responded with a status of 503 (Service Temporarily Unavailable)
Please help us resolve this issue so that users can continue using "Sign in with Apple" on iOS 12.
Recently, our some customers feedback that their phones can not connect to our site by safari or App, show the error of “Cannot Connect to Host -1004”,all of these problem customers has installed iOS 18.3 beta. Is there anybody meet the same problem?
Topic:
Safari & Web
SubTopic:
General
Hi!
I'm working on a web extension for Safari and I need to send messages from the containing application to JavaScript. For this I use the method
class func dispatchMessage(
withName messageName: String,
toExtensionWithIdentifier identifier: String,
userInfo: [String : Any]? = nil
) async throws
of the SFSafariApplication class. If the site is opened in Safari in normal mode, everything works as expected. However, if the site is "docked", the messages are not transmitted to this "Web App".
Hi all!
I have been working on a web speech recognition service using the Web Speech API. This service is intended to work on smartphones, primarily Chrome on Android and Safari (or WebKit WebView) on iOS.
In my specific use case, I need to set the properties continuous = true and interimResults = true. However, I have noticed that interimResults = true does not always work as expected in WebKit.
I understand that this setting should provide fast, native, on-device speech recognition with isFinal = false. However, at times, the recognition becomes throttled and slow, yielding isFinal = true and switching to cloud-based recognition.
To confirm whether the recognition is cloud-based, I tested it by disabling the internet connection before starting speech recognition. In some cases, recognition fails entirely, which suggests that requiresOnDeviceRecognition = false is being applied. (Reference: SFSpeechRecognitionRequest.requiresOnDeviceRecognition)
I believe this is not the expected behavior when setting interimResults = true. I have researched the native services used by the Web Speech API on iOS devices, and the following links seem relevant:
• SFSpeechRecognizer
• SFSpeechRecognitionRequest.shouldReportPartialResults
• SFSpeechRecognizer.supportsOnDeviceRecognition
• Recognizing speech in live audio
• Apple Developer Forums Discussion
I found that setRequiresOnDeviceRecognition and setShouldReportPartialResults appear to be set correctly, but apparently, they do not work as expected:
WebKit Source Code
Dears,
We are facing some issue in ios 18.4.1. Recently some of our end users who updated their ios devices to 18.4.1 have experienced random 403 errors in runtime. as per our analysis, We identified that these errors are associated with "CSRF token mismatch".
After successful login, the user's CSRF token is causing issue and it was changed in runtime, this causes the cookie mismatch, and the users is getting 403 errors, and the user session is getting invalid suddenly.
let me know if anyone facing the same issue in ios 18.4.1 and let me know Is there any workaround for this issue.
Thanks.
Hello Apple Developer Team,
I would love to see iCloud Keychain Autofill and Touch ID support extended to Chromium-based browsers on macOS (such as Ecosia, Brave, or Vivaldi).
Currently, Safari allows autofill of passwords using Touch ID, but when using other browsers, I have to manually copy-paste credentials from Keychain Access, which is time-consuming.
Would it be possible for Apple to provide an API or framework that allows non-WebKit browsers to integrate iCloud Keychain autofill while keeping security intact?
This feature would make macOS more convenient for users who prefer alternative browsers while keeping security standards high.
Thanks in advance for considering this!
Best regards, Kilian
We are experiencing a problem that seems to be caused by a specification changes for Safari.
We would like to discuss how to solve this problem.
Sample JavaScript:
<html>
<head>
<script>
function jumpPage(code) {
document.main.code.value = code;
win1=window.open("","win1","toolbar=no,resizable=yes,menubar=no,scrollbars=yes,status=yes,left=0,top=0");
win1.resizeTo(width=screen.availWidth,height=screen.availHeight);
document.main.action="details";
document.main.target="win1";
document.main.submit();
}
</script>
</head>
<body>
<form name="main" method="post" action="" target="">
<a href="javascript:jumpPage('001')">details</a>
<input type="hidden" name="code" value="">
</body>
</html>
This JavaScript performs the following actions when a link is clicked.
Open a window using window.open in JavaScript
Submit the above opened window by post method to the target in JavaScript.
When this operation is performed, the process in (2) could submit to the
target page with “POST” method before iOS18.1, but
will transition to the page with“GET”method from iOS18.2 onward.
All protocols are http.
This problem does not occur if the URL is specified as an IP address, but it does occur if the host name is specified as.
Please let me know how to use with“POST”method as in iOS 18.2 or earlier.
Best regards,
Topic:
Safari & Web
SubTopic:
General
Hello,
I’m working on a cross-origin WebAuthn implementation where a parent page embeds an iframe from a different origin to perform authentication. According to the WebAuthn Level 3 spec (Section 7.1.1), when crossOrigin is true, the clientDataJSON may include topOrigin—but Safari does not seem to populate this field.
Observed Behavior:
Chrome/Firefox: Include topOrigin in clientDataJSON when crossOrigin: true.
Safari (macOS/iOS): Omits topOrigin even though crossOrigin is correctly set to true.
Example clientDataJSON from Safari:
{
"type": "webauthn.get",
"challenge": "...",
"origin": "https://iframe-origin.example.com",
"crossOrigin": true
// Missing `topOrigin` (expected: parent origin)
}
Questions:
Is this an intentional omission in Safari for privacy/security reasons?
Are there specific requirements (e.g., HTTP headers, permissions policies) needed for Safari to expose topOrigin?
Is there a known workaround to reliably obtain the top-level origin in cross-origin WebAuthn flows?
System Info:
Version 18.4 (20621.1.15.11.10)
OS: Sequoia Version 18.4 (20621.1.15.11.10)
Reproduction Steps:
Parent page (https://parent.example.com) embeds an iframe (https://webauthn-rp.example.com).
The iframe calls navigator.credentials.get() with a WebAuthn challenge.
Safari returns clientDataJSON with crossOrigin: true but no topOrigin.
Code Snippet (iframe):
const credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array(/* ... */),
rpId: 'webauthn-rp.example.com',
allowCredentials: [],
hints: [],
userVerification: "preferred",
}
});
console.log(JSON.parse(atob(credential.response.clientDataJSON)));
Has anyone encountered this? Any insights would be greatly appreciated!
Topic:
Safari & Web
SubTopic:
General
When our Safari Web Extension makes a api request from its background script (registered via "scripts" in manifest.json, e.g., "background": { "scripts": ["js/background.bundle.js"] }) to our authenticated API endpoint (https://api-domain/user), the Cookie header is not included in the request. This occurs only when the extension is running within a non-default Safari User Profile. This causes our API to treat the user as unauthenticated. The exact same extension code, manifest, and API call work correctly (Cookie header is present and user is authenticated) when the extension is running in the Default Safari User Profile.
Hello,
I’m encountering a problem with WebSocket connections in Safari on iOS 18.1 and later when initiated from an iframe. The same implementation works perfectly in other browsers like Chrome but fails in Safari.
In Safari, the WebSocket connection fails with error message
"WebSocket connection to 'wss://MY_CONNECTION_URL' failed: The internet connection appears to be offline."
Has anyone else faced this? Is this a known limitation or bug in Safari? Any workarounds or solutions would be greatly appreciated.
Thank you!
Calling SFContentBlockerManager.reloadContentBlocker from related App extension intermittently fails
I have an app which has at least two extensions:
A Content Blocker extension with a request handler that returns an appropriate NSExtensionItem as part of beginRequest. A different file URL is returned depending upon if the content blocking is on or off by a user setting
A Safari Web Extension that includes a toolbar button and popover that enables users to enable or disable the ad blocking of the content blocker extension
All three targets (App, Content Blocker appex and Web Extension appex) use an App Group default to read and set the on or off status of the content blocking.
When the user changes the content blocking status, the app group default is updated and SFContentBlockerManager.reloadContentBlocker(...) is called.
The Content Blocker extension reads the default and then returns the appropriate file URL.
The issue is, I have noticed that whenever SFContentBlockerManager.reloadContentBlocker(...) is called from the app, Safari always applies the correct rules from the returned file URL.
However sometimes when SFContentBlockerManager.reloadContentBlocker(...) is called from the Safari Web Extension using native messaging, Safari does NOT apply the correct rules from the returned file URL.
Using logging I have confirmed that the Content Blocker extension always returns the appropriate file URL irrespective if called as a result of the app or the web extension.
Despite this, Safari does not seem to always apply the returned file URL rules when it is called from the Safari Web Extension appex. In these cases, quitting Safari and relaunching it seems to make it apply the rules correctly (obviously this is applying it due to its launch state, not due to the Web extension appex asking it to do so at that point).
All targets have access to the App Group location where the active content blocking file URL belongs and the inactive content blocking file URL is within the Safari content blocker target as a resource.
I don't think this is a memory status issue as I cannot see the Content Blocker extension being killed when it returns complex rules --- the fact it always works when called via the app also seems to rule this possibility out.
This brings up a number of questions:
Is calling SFContentBlockerManager.reloadContentBlocker(...) from a different appex, of the same app target and app group supported? (it seems to work sometimes and did work in previous versions of the app).
Is there an issue that the Content Blocker extension sometimes returns a file URL that perhaps the calling Web Extension appex may not have access to (even though Safari should via the Content Blocker extension)?
Any other ideas of why this may not be working correctly?
Has anyone else experienced this?
It seems to happen on both iOS and macOS Safari using the same codebase.
Hi everyone,
I'm facing an issue with accessing device orientation and motion events in Safari on my iPhone (iOS 18). Despite trying several guides and solutions, I cannot find the option to allow access to motion and orientation for websites in the browser settings. I’ve checked privacy settings, and the device is up to date. Can anyone guide me on how to enable this feature in Safari or share any workarounds? Thanks in advance!
I am calling fetch with a POST on page1 in Safari. No special cache parameters on the fetch call.
The response from the server is a 303 redirect to page2
The second page -- page2 -- is in my browser's cache with cache-control "public, max-age=31536000, immutable".
For some reason, the page2 redirect is causing a server hit to re-GET the second page every time instead of pulling from cache.
If I instead directly get the second page by doing a fetch on page2, there is no server hit.
If I do this on Chrome or Firefox, it behaves as I would expect, pulling page2 from the cache with no server hit.
In case it matters, the fetch is coming from within an iFrame. Also, if I change the original POST to a GET, the problem still happens.
I am using a pretty old version of Safari on my Mac, so I could chalk it up to that, but I am getting the same behavior with Safari on my iPhone with iOS 18.3.2
Any ideas?
Thanks.
Hello! I've made a Safari extension that supports command "ReTab", and a couple of month ago, adding a customized macOS shortcut for Safari with menu title "ReTab" did trigger the extension. However, it's not working anymore and I'm not sure if it's from macOS/Safari update or because I changed manifest from v2 to v3 - could you help check if there's anything wrong with either the manifest.json or background.js? (the default Cmd+E still works)
Thank you in advance!
Xun
manifest.json:
{
"manifest_version": 3,
"default_locale": "en",
"name": "ReTab",
"description": "Go to the last active tab with Cmd+E!",
"version": "1.4",
"homepage_url": "https://LycheeIsle.com",
"background": {
"service_worker": "background.js"
},
"action": {
"default_icon": "images/toolbar-icon.svg"
},
"permissions": [ "commands", "tabs", "storage" ],
"commands": {
"ReTab": {
"suggested_key": {
"default": "Command+E"
},
"description": "Go to the last active tab"
}
},
"options_page": "options.html"
}
in background.js, I have this line which should listen to the command, and Cmd+E works but any customized shortcut for "ReTab" in Safari doesn't:
browser.commands.onCommand.addListener(async (command) => {
if (command === "ReTab" || command === "retab") {
await retab()
}
});