In a project to create a web extension for Safari, using scripting.registerContentScript() API to inject a bunch of scripts into web pages, I needed to manage a dynamic whitelist (i.e., web pages where the scripts should not be injected).
Fortunately, scripting.registerContentScripts() gives you the option of defining a list of web pages to be considered as a whitelist, using the excludeMatches parameter in the directive, to represent an array of pages where the script should not be injected.
Here just a sample of what I mean:
const matches = ['*://*/*'];
const excludeMatches = ['*://*.example.com/*'];
const directive = {
id: 'injected-jstest',
js: ['injectedscript.js'],
matches: matches,
excludeMatches: excludeMatches,
persistAcrossSessions: false,
runAt: 'document_start'
};
await browser.scripting.registerContentScripts([directive])
.catch(reason => { console.log("[SW] >>> inject script error:",reason); });
Of course, the whitelist (the excludeMatches array) is not static, but varies over time according to the needs of the moment.
Everything works perfectly in Chromium browsers (Chrome, Edge, ...) and Firefox, but fails miserably in Safari. In fact, Safari seems to completely ignore the excludeMatches parameter and injects the script even where it should not.
Has anyone had the same problem and solved it somehow?
NOTE : To test the correctness and capabilities of the API in each browser, I created a simple repository on Github with the extension code for Chromium, Firefox and Safari (XCode project).
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hi all,
I need to detect if my web application (pure HTML / Javascript) is opened from:
Safari from a Mac
Safari from an iPad but by asking for the desktop version
I tried to check for many properties (including the navigator.useragent) but no difference were visible. Anyone could help me?
Thank you
Hi. I'm a developer of Tab Finder (https://apps.apple.com/us/app/tab-finder/id6741719894)
My problem is that every time i switch from my first window to a second window, the tabs in the validateToolbarItem() are INcorrect on a first call, but when I switch back from the second window to my main window, the tabs are CORRECT even on a first call.
To demonstrate it, i recorded a video: https://youtu.be/RwskzrSJ8u0
To run the same sample extension from the video, you can get the code from this GitHub repo: https://github.com/kopyl/test-tabs-change
Its only purpose is to log URLs of an active page of all tabs.
The SafariExtensionHandler's code of the sample app is very simple:
import SafariServices
func printOpenTabsHost(in window: SFSafariWindow) async {
let tabs = await window.allTabs()
log("Logging tabs for a new window: \(window.hashValue)")
for tab in tabs {
let page = await tab.activePage()
let properties = await page?.properties()
let url = properties?.url
log(url?.absoluteString ?? "No URL")
}
}
class SafariExtensionViewController: SFSafariExtensionViewController {
static let shared = SafariExtensionViewController()
}
class SafariExtensionHandler: SFSafariExtensionHandler {
override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) {
Task {
await printOpenTabsHost(in: window)
}
validationHandler(true, "")
}
override func popoverViewController() -> SFSafariExtensionViewController {
return SafariExtensionViewController.shared
}
}
Could you please tell if i'm missing something and how to see the actual tabs inside the overridden validateToolbarItem call of the SafariExtensionHandler (or in any other way, I'm okay with any implementation as long as it works).
Topic:
Safari & Web
SubTopic:
General
Tags:
Extensions
Safari Services
Safari and Web
Safari Extensions
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
When i use adjust redirect:
https://app.adjust.com/xxxxxx?label=xxxxxx&redirect=http%3A%2F%2Fwww.testingmcafeesites.com%2Ftestcat_bu.html
It open 2 links:
https://Fwww.testingmcafeesites.com
then http://www.testingmcafeesites.com/testcat_bu.html
And in my app use redirect link for open a web page. But content in domain url like https://www.testingmcafeesites.com/ not be set. So it talke long time often 1 minute for finish request in first link.
It hapen only in ios 18 i tested in ios 17 and ios 16 it open one link only.
I'm experiencing issues with audio playback in my React video player component specifically on iOS mobile devices (iPhone/iPad). Even after implementing several recommended solutions, including Apple's own guidelines, the audio still isn't working properly on iOS Safari. It works completely fine on Android. On iOS, I ensured the video doesn't autoplay (it requires user interaction). Here are all the details:
Environment
iOS Safari (latest version)
React 18
TypeScript
Video files: MP4 with AAC audio codec
Current Implementation
const VideoPlayer: React.FC<VideoPlayerProps> = ({
src,
autoplay = true,
}) => {
const videoRef = useRef<HTMLVideoElement>(null);
const isIOSDevice = isIOS(); // Custom iOS detection
const [touchStartY, setTouchStartY] = useState<number | null>(null);
const [touchStartTime, setTouchStartTime] = useState<number | null>(null);
// Handle touch start event for gesture detection
const handleTouchStart = (e: React.TouchEvent) => {
setTouchStartY(e.touches[0].clientY);
setTouchStartTime(Date.now());
};
// Handle touch end event with gesture validation
const handleTouchEnd = (e: React.TouchEvent) => {
if (touchStartY === null || touchStartTime === null) return;
const touchEndY = e.changedTouches[0].clientY;
const touchEndTime = Date.now();
// Validate if it's a legitimate tap (not a scroll)
const verticalDistance = Math.abs(touchEndY - touchStartY);
const touchDuration = touchEndTime - touchStartTime;
// Only trigger for quick taps (< 200ms) with minimal vertical movement
if (touchDuration < 200 && verticalDistance < 10) {
handleVideoInteraction(e);
}
setTouchStartY(null);
setTouchStartTime(null);
};
// Simplified video interaction handler following Apple's guidelines
const handleVideoInteraction = (e: React.MouseEvent | React.TouchEvent) => {
console.log('Video interaction detected:', {
type: e.type,
timestamp: new Date().toISOString()
});
// Ensure keyboard is dismissed (iOS requirement)
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
e.stopPropagation();
const video = videoRef.current;
if (!video || !video.paused) return;
// Attempt playback in response to user gesture
video.play().catch(err => console.error('Error playing video:', err));
};
// Effect to handle video source and initial state
useEffect(() => {
console.log('VideoPlayer props:', { src, loadingState });
setError(null);
setLoadingState('initial');
setShowPlayButton(false); // Never show custom play button on iOS
if (videoRef.current) {
// Set crossOrigin attribute for CORS
videoRef.current.crossOrigin = "anonymous";
if (autoplay && !hasPlayed && !isIOSDevice) {
// Only autoplay on non-iOS devices
dismissKeyboard();
setHasPlayed(true);
}
}
}, [src, autoplay, hasPlayed, isIOSDevice]);
return (
<Paper
shadow="sm"
radius="md"
withBorder
onClick={handleVideoInteraction}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
>
<video
ref={videoRef}
autoPlay={!isIOSDevice && autoplay}
playsInline
controls
crossOrigin="anonymous"
preload="auto"
onLoadedData={handleLoadedData}
onLoadedMetadata={handleMetadataLoaded}
onEnded={handleVideoEnd}
onError={handleError}
onPlay={dismissKeyboard}
onClick={handleVideoInteraction}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
{...(!isFirefoxBrowser && {
"x-webkit-airplay": "allow",
"x-webkit-playsinline": true,
"webkit-playsinline": true
})}
>
<source src={videoSrc} type="video/mp4" />
</video>
</Paper>
);
};
Apple's Guidelines Implementation
Removed custom play controls on iOS
Using native video controls for user interaction
Ensuring audio playback is triggered by user gesture
Following Apple's audio session guidelines
Properly handling the canplaythrough event
Current Behavior
Video plays but without sound on iOS mobile
Mute/unmute button in native video controls doesn't work
Audio works fine on desktop browsers and Android devices
Videos are confirmed to have AAC audio codec
No console errors related to audio playback
User interaction doesn't trigger audio as expected
Questions
Are there any additional iOS-specific requirements I'm missing?
Could this be related to iOS audio session handling?
Are there known issues with React's handling of video elements on iOS?
Should I be implementing additional audio context initialization?
Any insights or suggestions would be greatly appreciated!
I want to migrate from a Safari App Extension to a Safari Web Extension, but don't know how to get rid of the message, telling users that my extension can access their passwords. Here is a message which I see:
I was thinking that this might be because all Safari Web Extension get this type of access, but I have a Safari Web Extension which does not require such level of access:
Here is the manifest:
{
"manifest_version": 2,
"default_locale": "en",
"name": "__MSG_extension_name__",
"description": "__MSG_extension_description__",
"version": "1.1",
"icons": {
"48": "images/icon-48.png"
},
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/toolbar-icon-16.png"
}
},
"permissions": [
"nativeMessaging", "tabs"
]
}
and here is the Info.plist file:
Here is the entire code of the extension:
https://github.com/kopyl/web-extension-simplified
I have an app that has a WKWebView for watching YouTube videos. When the videos are windowed the audio seems fine, positionally as well. All perfectly.
When I fullscreen the video and it goes into the native visionOS video player the audio messes up.
It will suddenly sound like it is in your ears, or maybe even just one ear channel, or the position will be wrong. It might be fine for a moment but the second I touch the controls or move the window the sound jumps across the room, away from the window, or switches to stereo.
Sometimes exiting windows entirely you will still hear the videos playing. Even if you open the window back up and go to another screen and open another video, now you hear 2 videos playing at the same time with no way to stop the first one in the background, requiring to force restart the app.
It is all sorts of glitchy. I haven't the slightest clue what is happening here. I am strongly feeling this is a visionOS bug.
I tried using AVAudioSession to change some of the sound settings, and that makes zero difference in behavior.
Multiple testers have also reported this behavior and it has been seen on both visionOS 2.3 and 2.4 betas.
Thanks for the help! This is driving me mad! It is extremely consistent behavior!
From a mail app or similar, when opening a webpage in Safari as an external browser, JavaScript on the webpage stops running if Safari goes into the background. Is there a way to prevent this from happening?
Sample code for the counter:
Behavior: Upon returning from the background, the counter continues for about 7-8 seconds but does not progress further.
For example, if Safari is kept in the background for about 20 seconds and then brought back, the counter stops at around 7-8 seconds and only resumes counting after returning to the foreground.
Expectation: The counter should continue running even if Safari goes into the background.
Good morning fellow developers,
For a while i am struggeling with providing sound to my users on IOS (Safari on Mac is no problem and every other device is not a problem) (we have an existing phone system and made a chat as well), the case is very simple: the notification sound is only for users who are logged in and online for chat.
i have tried multiple things:
Audio play with javascript (start with mute, play when user clicks a button so the sound is familiar, play when user clicks a button and directly pause it and continue when needed)
PWA: the dashboard has been made available as pwa and notifications using google firebase. The popup does show for notifcations to be allowed (and receiving the notifications does work on any other device) But any IOS device cannot register.
The information i find is that notifications were supported with 16.4 or higher but also have been deprecated around IOS 17, auto play is not allowed.
We have an app in development for our product as well were we will have a notification which will handle this, but that is not the solution we can use now.
Long story, short question: is it still somehow possible to push a notification to the user when using the PWA or play a sound in the browser (based on an ajax function). The app/website wont be in the background, so it will always be on the screen.
Languages we use: html/javascript (mostly vanilla)/php
I am trying to build and run a Safari Web Extension from Xcode and I have enabled "Allow unsigned extensions" in Safari settings. However, I see the below pop up:
And, if click on the "Quit and Open Safari Extensions Preferences..." button, the project stops running on Xcode and nothing happens.
What can be the issue? The extension works and runs fine if I get it from the Mac App Store and this only happens when running from Xcode. I even tried completely uninstalling the mac app store version and still facing the same issue.
Please update Accessibility OS Settings for VoiceOver in iPhone iOS and iPadOS to include frames on the Rotor, and to make web navigation and component gestures easier to find and assign. Please add content to the iPhone and iPad Apple User Guide to use VoiceOver in web navigation with touch gestures.
Specifically... iframes.
There is no clear guidance in Apple documentation for VoiceOver users in iPhone or iPadOS to access iframes with touch gestures. A common belief as written on AppleVis, other blogs, and internet searches is that iframes in Safari or a webView in an app are only available with explore by touch.
If explore by touch is the only option for some interactions, that needs to be included in Apple User Guides. If not, details on equivalent touch gestures for VO that have keyboard interactions in Mac need to be clear for users.
VoiceOver for Mac includes a default keyboard interaction of VO-Command-F in its extensive User Guide (https://support.apple.com/guide/voiceover/by-images-or-frames-mchlp2740/mac). A user can include a rotor option for web navigation for iframes.
VoiceOver for iPhone and iPad does not include a default swipe gesture assigned to frames. An option is not available for the Rotor.
While there is iPhone User Guide guidance that gestures can be customized (https://support.apple.com/guide/iphone/customize-gestures-and-keyboard-shortcuts-iph59a8e6fd2/18.0/ios/18.0), it is not clear that for adding this gesture, "Move to the next frame" is tucked into the advanced navigation commands for VoiceOver Accessibility Settings in the OS. At least in my phone, the word "frame" was not searchable despite the All Commands screen using a search bar.
After updating to iOS 18.4, our web application (with service workers) crashes on devices that accessed it prior to the update. This issue also affects hybrid mobile apps using the same web application; reinstalling the app resolves it by refetching and reinstalling service workers. Debugging is challenging because clearing the cache or reinstalling the app fixes the problem, and no useful error logs are available. Has anyone encountered similar crashes related to service workers after an iOS update and have any insights into the cause?
So I have web Augmented Reality apps hosted on AWS S3. It worked fine for a month, but as soon as the IOS 18.4 update was installed they stopped working. It works on every other device and IOS versions.
The URLs for the mentioned AR experiences:
digitechonline.in/solsprefimaginewt8/
digitechonline.in/solsprefimaginewt8p2/
digitechonline.in/orocarear/
These AR experiences get stuck on the loading screen and either reload or give an error. Ideally the camera is supposed to open.
I have tested it on Safari, Microsoft Edge and Google Chrome browsers.
They were created through Unity webgl and hosted on AWS S3 bucket. Please provide a quick solution to this.
When a DNR rule is set for a specific URL and the request receives a server-side redirect (e.g., 302) to a different URL that does not match the urlFilter, the rule still seems to apply to the redirected request. We are using macOS 15.4 and Safari 18.4.
For example, consider two sequential calls: call1 and call2.
call1 triggers a 302 redirect to call2.
A DNR rule is created to add a "Cookie" header to call1 based on its URL.
Unexpectedly, the same cookie is also added to call2, even though call2's URL does not match the rule's urlFilter.
This results in the Set-Cookie response from call1 being ignored, and call2 receiving the manually set cookie instead—leading to incorrect behavior.
This issue doesn't occur in Chrome or Firefox, where the rule is not applied to the redirected request if the URL no longer matches.
We are looking for assistance in fixing these issues and having our Safari Extension function the same as it does in Firefox and Chrome.
We’re encountering an issue when trying to add non-standard headers to outgoing requests using Declarative Net Request (DNR) rules in our Safari Web Extension.
Tested on macOS 15.4 with Safari 18.4.
Specifically, when attempting to add a custom header such as "X-Custom-Header" using a DNR rule, the header does not appear in the request. We are able to add standard headers like "Authorization" and "Cookie" to the request successfully using the same method.
This behavior suggests that Safari may be filtering or blocking non-standard headers when set via DNR rules, unlike other browsers.
In Chrome and Firefox, the same rule adds the "X-Custom-Header" header without any issue.
We are looking for assistance in fixing these issues and having our Safari Extension function the same as it does in Firefox and Chrome.
Safari Extension Error: “Non-persistent background content cannot listen to webRequest events.” after macOS 15.4 / Safari 18.4 Update
We’re seeing the following error in the Safari Extensions tab after updating to macOS 15.4 and Safari 18.4:
“Non-persistent background content cannot listen to webRequest events.”
This error did not appear prior to the update, and we haven’t found any official documentation stating that webRequest API is no longer supported in Safari.
In our extension (Manifest V3), we are using the webRequest.onHeadersReceived callback to intercept response headers and read updated cookies.
While the functionality itself still works as expected. we’re able to access the response headers and this error is now shown in the Extension settings page.
We are not seeing this issue in other browsers (Chrome, Firefox) using the same Manifest V3 setup.
Is there any plan to deprecate webRequest support in Manifest V3 for Safari?
We’d appreciate any clarification or guidance on how to handle this going forward.
Howdy,
WKWebView feature request: allow Fullscreen API without User Gestures
similar to ElectronJS' userGesture: true flag that allows devs to bypass user gesture restriction for Fullscreen API and similar
executeJavaScript(code[, userGesture])
https://www.electronjs.org/docs/latest/api/web-contents#contentsexecutejavascriptcode-usergesture
afaik this is allowed because of a fairly recent update to Chromium that also allows users to give Fullscreen API permissions per domain
https://chromeos.dev/en/posts/using-the-fullscreen-api-without-gestures
Would be greatly useful for a use case in my cross-platform app, so I can avoid rewriting all platforms to use Chromium
Thanks
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!
updateDynamic rules is blocking ads on my device with iOS 17.4, but on my iOS 18 device the same code is not blocking ads.
Is this a known issue?