I'm very excited about bring web extensions back to Safari. I have an existing Safari App Extension and would like to port it back to Safari Web Extensions. I have a question about backwards compatibility. Will Safari 14 be backwards compatible with older OS such as High Sierra or Mojave?
Post
Replies
Boosts
Views
Activity
I'm trying to convert a Safari App Extension to the newer Safari Web Extension API, and having an issue with an injected iFrame we use to protect user data. Inside our provided iFrame which which we source from:
safari-web-extension://<ID_HERE>/<HTML_FILE_HERE>
and is externally_connectable via the manifest.json.
Any scripts that run inside the iFrame are able to initiate communication with background scripts with no problem. However, any message initiated from background never registers on any onMessage listener. For example:
// iframe.js
// WORKS FINE
browser.runtime.sendMessage({ greeting: "hello" })
.then((response) => {
		console.log("Received response: ", response);
});
// NEVER FIRES
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
		console.log("Received request: ", request);
});
I'm convinced this is an Apple bug because this same scenario for the App Extensions receives the request from the background scripts no problem. Can anyone prove me otherwise?
I'm trying to find a way to block any requests to domains my app decides not to trust. I've discovered the default capabilities of App Transport Security - https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity, which is a great start, but would still allow a remote connection to a third party that uses HTTPS.
I ask this in the context of a Safari App Extension, although I'm sure this could apply to any macOS/iOS application. Web extensions (Chrome/Firefox/Edge) already provide this capability, and I'd like to secure my application accordingly.
Background pages for Safari Web Extensions appears to be unable to load a local bundle resource to create a Web Worker script, even when the script is included in the web_accessible_resources in the manifest, as well as the worker-src in the content_security_policy.
I have no problem doing so on the other web extension platforms (Chrome, Firefox) with the same extension and code.
I've already filed a bug for this FB11955055, wondering if anyone else has been faced with this problem. It seems similar in nature to this issue https://stackoverflow.com/questions/73399285/safari-extension-background-page-unable-to-fetch-web-accessible-resource, but that one Apple claims to have resolved in Safari 16.
Edit: added error message
When you have a blank Safari new tab open, the browser.tabs.query API inaccurately includes those tabs in results that include a url or title in the query options.
To reproduce, open several tabs, and a blank new tab. Open the background page devtools, and execute the following command:
browser.tabs.query({url: 'https://github.com/'}, console.log)
In addition to any potentially valid results (if you have github.com open, for example), there will also be an entry for the blank tab, which has an empty, non-matching URL.
Tab {
active: true,
audible: false,
height: 1095,
highlighted: true,
id: 6,
incognito: false,
index: 3,
isArticle: false,
isInReaderMode: false,
mutedInfo: {muted: false},
pendingUrl: "",
pinned: false,
status: "complete",
title: "", url: "",
width: 1792,
windowId: 1
}
I hope that this bug can be addressed, as it causes some unexpected behavior.
Messages intended for a port connection created in content scripts are unable to receive messages from the extension background script.
Consider a content.js and background.js with the following contents:
content.js:
const port = chrome.runtime.connect({
name: 'TEST'
})
// THIS IS NEVER RECEIVED
port.onMessage.addListener((message) => {
console.log('RECEIVED TEST MESSAGE', message)
})
background.js:
chrome.runtime.onConnect.addListener((port) => {
if (port.name !== 'TEST') return
console.log('test port connected', port)
console.log('SENDING PORT MESSAGE')
port.postMessage('HELLO')
})
This behavior was broken in Sequoia, Safari 18. This behavior also does not match that of Firefox and Chrome, which are able to receive port messages in content scripts.
It's also worth noting that UI documents with the same origin as the extension, such as a popup or iFrame, ARE able to use the port messaging as expected.
However, this bug is a huge regression and should really be addressed. I've already filed an issue via Apple Feedback with the ID of FB14721836, over a month ago, but never received a response. I'm posting here for more visibility and hope a fix can be included before Sequoia goes live next week.
Typically, you can use the @@extension_id special string to reference the absolute path into the bundled resources of an extension, such as an image or a custom font, in a CSS file.
However, this broke with Safari 18.
Consider this section in a popup.css file:
.card-icon {
height: 16px;
width: 20px;
background-image: url(safari-web-extension://__MSG_@@extension_id__/images/card.svg);
background-size: 20px 16px;
}
In Safari 17.4, once loaded in the browser, @@extension_id is replaced with E8BEA491-9B80-45DB-8B20-3E586473BD47, and the background-image reads as so:
background-image: url(safari-web-extension://E8BEA491-9B80-45DB-8B20-3E586473BD47/images/card.svg);
But as of Safari 18, the @@extension_id just collapses to an empty string, and the background-image reads as so:
background-image: url(safari-web-extension:///images/card.svg);
and the svg fails to load with the following error: "Failed to load resource: You do not have permission to access the requested resource."
This is a regression, does to match the behavior of the other major browsers, and should be fixed.
Filed with Feedback ID: FB15104807
As of Safari 18, the tabs.executeScript extension API no longer respects the frameId option passed to it, and instead just runs the script in the top frame.
Try running an extension with the following contents on a page with an iFrame (chase.com puts its login form in an iFrame for example).
content.js
console.log("content.js loaded", location.href);
// tabs.executeScript test
browser.runtime.sendMessage({ type: "tabs.executeScript" }).then(() => {
console.log("tabs.executeScript done");
});
// scripting.executeScript test
browser.runtime.sendMessage({ type: "scripting.executeScript" }).then(() => {
console.log("scripting.executeScript done");
});
background.js
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === "tabs.executeScript") {
browser.tabs
.executeScript(sender.tab.id, {
code: 'console.log("THIS IS RUN FROM TABS.EXECUTESCRIPT", location.href);',
frameId: sender.frameId,
})
.then(sendResponse);
} else if (request.type === "scripting.executeScript") {
browser.scripting
.executeScript({
target: { tabId: sender.tab.id, frameIds: [sender.frameId] },
func: () => {
console.log("THIS IS RUN FROM SCRIPTING.EXECUTESCRIPT", location.href);
},
})
.then(sendResponse);
}
});
You'll see that tabs.executeScript runs its contents in the TOP frame, no matter what the target is.
Notably, scripting.executeScript DOES respect the frameId.
Filed with Feedback ID FB15420092