I'm making a Safari extension for learning languages. I need speech synthesis for any language the user chooses to learn.
I initially tried to make this work within JavaScript, but Safari 18 doesn't reliably list voices for all languages on the web SpeechSynthesis API as described here: https://stackoverflow.com/questions/79179072/how-do-you-use-a-japanese-voice-with-speechsynthesis-in-safari-ios-18
As a workaround, I've had to use AVSpeechSynthesizer in SafariWebExtensionHandler (NSExtensionRequestHandling implementation for the extension). This works in the simulator but not on a real device. I've found this note from Apple in a StackOverflow reply:
"Safari extensions are very short-lived, hence not fit for audio playback or speech synthesis. Not being able to validate an app extension in Xcode with a manually-added plist entry for background audio is the designed behavior. The general recommendation is to synthesize speech using JavaScript in conjunction with the Web Speech API."
Unfortunately, the suggestion to use the Web Speech API is unsuitable as I just explained.
Is there a way to set up a background process in the host app that can do speech synthesis? The app extension would need a way to communicate with this process, and start it if it's not running. Is that possible?
Safari Extensions
RSS for tagEnhance and customize the web browsing experience on Mac, iPhone, and iPad with Safari Extensions
Posts under Safari Extensions tag
113 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I'm porting to Safari a Chrome/Firefox Extension that makes use of declarativeNetRequest.updateDynamicRules to remove some HTTP headers from requests to a specific URL.
This works mostly fine, except for some headers for which this is not allowed by WebKit, such as the Priority header (which is however served by Safari).
An annoying corner-case I found is that of the Cookie header. When trying to remove it from the request by adding the following rule
{
id: <rule id>,
priority: <rule priority>,
action: {
type: "modifyHeaders",
requestHeaders: [{ "header": "Cookie", "operation": "remove" }]
},
condition: {
urlFilter: <my url>,
resourceTypes: ["main_frame", "sub_frame"]
}
}
nothing error is thrown, yet the Cookie header is still being sent. This rule however works for other headers, such as Referer. Changing "Cookie" for "cookie" does not help.
Questions:
Is there an alternative way of removing the Cookie header from the HTTP request?
Is this just a bug in WebKit?
Is there any way of removing "unsupported" headers such as Priority?
Any help or references are appreciated.
Edited: more specific description of the corner-case.
Hello,
We have a Safari extension in the app store for about two years.
Our extension relies on syncing the data available in a desktop application to the extension.
For this we always used SFSafariApplication.dispatchMessage to sync the data without the extension requesting it.
And it used to work just fine.
Now it appears that dispatchMessage is being marked as unavailable to extensions, so now xcode 16 is failing to build the extension.
Also the documentation (Send messages from the app to JavaScript) still indicates that we can use dispatchMessage to initiate communication from the mac os app.
Is there a way to achieve this as it is required for our extension to function, and that's how we have built it for chrome and firefox (one codebase).
My existing chrome extension has "Sign in with Apple" given that we have iOS users.
When user clicks "Continue with Apple" button in the extension log in pop up, this is what we do:
javascript
window.open(
'https://appleid.apple.com/auth/authorize?client_id=' + clientID + '&redirect_uri=' + backEndURL + '&response_type=id_token%20code&response_mode=form_post&scope=email%20name',
'Sign in with Apple', 'height=500,width=400,left=600,top=200,status=no,location=no,toolbar=no,menubar=no'
)
In chrome, this opens a popup window with that URL.
In Safari Converted Web Extension, it opens custom Apple sign in flow, where it says:
"Do you want to sign in to XXX with your Apple ID YYY?"
and then with my mac password I'm able to authenticate.
Afterwards, nothing happens.
Expected: a redirect to the URL specified in the window.open.
Now let's do a trick:
I'll wrap the above window.open code into
javascript
setTimeout (() = {window.open (...)}, 3000)
Because of security reasons, safari then won't open the popup after 3s and will display a notification in the toolbar "Popup blocked..".
If we allow the popup, then it finally opens as a normal window popup and after sign in, it redirects to our backend and it successfully authenticates.
Any ides what how to solve this?
P.S. We're not able to use embedded Sign in with Apple JS - https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/configuring_your_webpage_for_sign_in_with_apple script because we can't host a remote code in the extension (it will be deprecated soon). So, we arere using this. - https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms
Topic:
Privacy & Security
SubTopic:
General
Tags:
Sign in with Apple
Sign in with Apple JS
Safari Extensions
Sign in with Apple REST API
Hello.
Environment
Safari version 18.1 (22B81).
Problem
In my Safari Web Extension i need to determine the url of the active tab.
I do it this way:
const onError = (error) => {
console.error(error);
};
const getFrameUrl = async (frames) => {
console.info(frames)
};
const getAllFrames = (frames) => {
browser.webNavigation.getAllFrames({ tabId: frames[0].id }).then(getFrameUrl, onError);
};
browser.tabs.onUpdated.addListener(() => {
browser.tabs.query({ currentWindow: true, active: true }).then(getAllFrames, onError);
});
The problem is that if the tab url is a link in the blob protocol, then the result function browser.webNavigation.getAllFrames({ tabId: tabs[0].id }) is as follows:
{frameId: 0, parentFrameId: -1, url: "", errorOccurred: false}
In Safari version 17 the correct URL value was returned:
{frameId: 0, parentFrameId: -1, url: "blob://<some id>", errorOccurred: false}
How can I fix the problem?
Environment
Safari version 18.1 (22B81).
iOS16 and iOS17 were fine, but on iOS18, our Safari extension that blocks content via static rulesets randomly stops working. Frequently, when a tab is left in the background for a long time (i.e. hours), the content blocker will stop working (until I either kill safari, or reload the extension). I've debugged this and the background.js script reports the ruleset as being loaded, but nevertheless, our rules aren't applied.
I really don't think that it's an issue with the way that the rules are defined, since iOS16 and iOS17 worked fine, and on iOS18, the rules DO work. They just stop working after a while.
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_1",
"enabled": true,
"path": "ruleset_1.json"
}
]
},
Some theories:
I have other content blockers on my phone that have LOTS of rules (adguard). Could I be seeing the effects of too many rules? Can I debug this somehow? Do logs get printed somewhere when the max rule limit is reached?
Does the use of private and regular tabs mess things up?
Please, any input is appreciated, as all of our logs are normal and error-free.
I'm porting a web extension from Chrome to Safari, and I need to execute a method as a content script that is part of an ES module (in particular its default export).
In Chrome I use following snippet (via Stack Overflow)
const results = await chrome.scripting.executeScript({
args: ["/js/content_script/main.js"],
func: async (path) => {
const src = chrome.runtime.getURL(path);
const { default: asyncMain } = await import(src);
return asyncMain();
}
});
In Safari this seems to either not work, cause the Web Extension Background Context window to close, or crash the browser.
Am I doing something wrong, or is this not possible?
Starting Safari 18, Declarative Net Request redirections are no longer working (it used to be working on Safari 17). It seems to be related to the regex validation that is not working as expected.
Here is an example of our DNR rule:
{
priority: 1,
action: {
type: 'redirect',
redirect: {
regexSubstitution: `${scheme}//${extensionHost}/index.html\\1#/\\2`,
},
},
condition: {
// app.dashlane.com, *.app.dashlane.com
regexFilter: '^https?://w*\\.?app\\.dashlane\\.com(\\??[^/#]*)[^#]*#?/?(.*)$',
resourceTypes: [
'main_frame'
],
},
}
Is it a known issue ?
Also, it seems to be related to this existing issue:
https://forums.developer.apple.com/forums/thread/763505
Hi,
I have a Safari extension that works well in iOS before 18, which can switch to a different tab with chrome.tabs.update back and forth by keyboard shortcuts. With iOS 18, as Page content loses its focus, it is impossible to trigger content script directly, I have to manually touch screen to put focus back to page content so that keystroke can trigger my content script to talk with background to call chrome.tabs.update to switch back.
Please bring focus to page content after switching to a new tab with chrome.tabs.update.
Hello everyone,
I’m working on adding a Safari extension as an application extension to an iOS app created using Capacitor (generated from a React app). My goal is to bundle the Safari extension within the Capacitor app, allowing it to serve as a companion app for the extension. However, I’ve run into issues where the extension does not show up in Safari's extension settings when I run the app on a device or simulator.
Here's an overview of what I’ve done so far:
I initially used Capacitor’s command line tools to generate the iOS app from my React project. This app runs fine on its own.
To add the Safari extension, I’m not developing a platform-specific extension from scratch; instead, I used the Xcode command line to convert a Chrome extension to a Safari iOS extension. The converted extension functions correctly if paired with the default Swift app that comes with the conversion.
Right now, I effectively have two products:
The base Capacitor app (without the extension), which I want as my main app.
The standalone Safari extension bundled with the Swift app (from the conversion process).
My question is: How can I merge these two products so that the Capacitor app becomes the companion app for the Safari extension, allowing the extension to show up in Safari's settings? Are there specific configurations I should adjust in Xcode or within the manifest files to make this work seamlessly?
Any guidance or best practices from others who have integrated Safari extensions into non-Xcode-based projects would be much appreciated!
Thank you!
I've observed a discrepancy in cookie consent options between iOS 18.0 and 18.1 on some websites, such as www2.hm.com. On iOS 18.0, I see "Accept All Cookies" and "Only Required Cookies" options, whereas on iOS 18.1, the options change to "Accept All Cookies" and "Cookie Settings."
I would like to understand if this behavior is related to differences in how websites detect the operating system version (iOS 18.0 vs. 18.1) or browser changes within the iOS update. Has anyone else experienced similar variations in cookie consent banners, and could this be tied to differences in the user agent or website A/B testing for different OS versions?
Any insights or technical clarifications would be appreciated!
I am building Safari extension. In my background script I am setting badge text and title like this:
browser.action.setBadgeText({text: badgeText, tabId: tabId});
browser.action.setTitle({title: badgeText + " found images", tabId: tabId})
, where tabId is correct id of current tab.
I was expecting that in this way I am setting a different badge and title for different tabs, so that badge and title would automatically switch if I activate different tab. However this does not happen, badge and title behave as if set globaly and don't change with tabs. How is this expected to work?
I have also tried to set badge globally, and update it every time user switches a tab. I have set up listener like this:
browser.tabs.onActivated.addListener(function(actInfo) {
console.log("switched tab to " + actInfo.tabId);
});
, however the event never fires, tab switch is never logged in console.
Am I doing something wrong here?
This is my manifest, if there was a problem with permissions or something similar.
{
"manifest_version": 3,
"default_locale": "en",
"name": "Test",
"description": "Test Extension",
"version": "1.0",
"icons": {
"48": "images/icon-48.png",
"96": "images/icon-96.png",
"128": "images/icon-128.png",
"256": "images/icon-256.png",
"512": "images/icon-512.png"
},
"action": {
"default_title": "a test title",
"default_popup": "popup/hello.html",
"default_icon": {
"16": "images/toolbar-icon-16.png",
"19": "images/toolbar-icon-19.png",
"32": "images/toolbar-icon-32.png",
"38": "images/toolbar-icon-38.png",
"48": "images/toolbar-icon-48.png",
"72": "images/toolbar-icon-72.png"
}
},
"web_accessible_resources": [
{
"matches": ["*://*/*"],
"resources": ["images/*", "css/*", "scripts/lib/*"]
}
],
"background": {
"service_worker": "scripts/background.js",
"type": "module"
},
"content_scripts":
[
{
"js": [
"scripts/content.js"
],
"matches": [
"http://*/*",
"https://*/*"
],
"css": ["css/style.css"],
"run_at": "document_end"
}
],
"permissions": [
"nativeMessaging", "tabs"
]
}
Hello,
I'm currently facing some issues with localization for the Safari extension on iOS:
Issues with Language Tags:
Folder names like pt-BR (Brazilian Portuguese) and pt-PT (European Portuguese) placed in the Resources/_locales/ directory are not displaying the respective languages correctly; instead, the default English is shown.
Similarly, using folder names like zh-CN (Simplified Chinese) and zh-TW (Traditional Chinese) also results in default English display instead of the intended Chinese language.
Conversely, when changing the folder names to pt (Portuguese general) and zh (Chinese general), the languages display correctly.
Could you please provide any recommendations or tips regarding language tag settings and how to ensure they are properly recognized according to RFC 5646?
Thanks for your help!
Best,