Safari Extensions

RSS for tag

Enhance and customize the web browsing experience on Mac, iPhone, and iPad with Safari Extensions

Safari Extensions Documentation

Posts under Safari Extensions tag

214 Posts
Sort by:
Post not yet marked as solved
2 Replies
119 Views
Hi, I am trying to test along the tabs onUpdated event on a simple web extension/addon under Chrome. I used the Safari XCRUN converter ! What I am trying to do is : 1- Open new tab on Google Scholar with set prefs params, from "options.js" script (code below) 2- Listen for tab to be updated and ready (e.g. tab status is complete) 3 - Then, inject a content script that will simulate the user click on save button 4- Then wait 1,5s (for GS tab to reload and finish saving) and remove the listener 5- Finally close this GS tab and back to extension Options page tab. // Detect browser language const gsUrl = currentBrowser.i18n.getUILanguage().includes("fr") ? GSCHOLAR_SET_PREFS_FR_URL : GSCHOLAR_SET_PREFS_COM_URL; // Listener to detect when the GS tab has finished loading const gsTabListener = (tabId, changeInfo, tabInfo) => { if (changeInfo.url && changeInfo.url.startsWith(GSCHOLAR_HOST)) { currentBrowser.tabs.executeScript( tabId, { code: `document.getElementsByName("save")[0].click();`, }, () => { currentBrowser.tabs.onUpdated.removeListener(gsTabListener); setTimeout(() => currentBrowser.tabs.remove(tabId), 1500); } ); } }; currentBrowser.tabs.onUpdated.addListener(gsTabListener); // Add tab listener currentBrowser.tabs.create({ url: `${gsUrl}?inst=${gScholarInstIdList.join("&inst=")}&save=#2`, active: false, }); // Open GS tab according to browser language The problem is that it works well on Chrome/Edge/Firefox, but not Safari : the GS tab isn't closed and nothing happens :-/ PS: Of course tabs onUpdated event is well supported on Safari according to MDN. https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated I have also tried webNavigation onCompleted event, but same ! https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation/onCompleted Thanks for your feedback.
Posted
by mhabsaoui.
Last updated
.
Post not yet marked as solved
0 Replies
58 Views
I have a working Chrome extension using manifest version 3, and I am following the porting instructions to turn it into a Safari extension on MacOS (Monterey) - using xcrun from the Xcode toolset. I have Safari v. 15.5. There are two problems. First, the documentation says "Safari 15.4 and later supports manifest versions 2 and 3". So how come xcrun complains about all of the following keys: manifest_version, icons, description, version, js, matches, service_worker, type, action, tabs, activeTab, storage, alarms, webNavigation, web_accessible_resources, name, css? At least some of those are basic to v3 manifests. Second, when I try to install it in Safari (it builds OK under Xcode despite the above "problem") I get an error. In my background (service worker) script I call self.importScripts("Platform.js"); which should load said JavaScript file. It's in the same folder as the script that calls it. This works in Chrome but fails in Safari with the error: Failed to load resource: unsupported URL safari-web-extension://FE580C4D-9931-4639-ABF9-...../Platform.js I tried changing that dynamic import to a static import: import Platform from "./Platform.mjs"; (After converting Platform.js to a module Platform.mjs.) This also works on chrome but now my converted extension for Safari won't even load. I get: The service_worker script failed to load due to an error. But I can find no way of determining what that error is (there is no information in the service worker console). I would appreciate help with either or both of these problems. Perhaps they are connected and there is something missing/wrong about my version 3 manifest - even though Chrome is happy with it.
Posted Last updated
.
Post not yet marked as solved
0 Replies
55 Views
On macOS we have SFSafariExtensionManager and class func getStateOfSafariExtension(withIdentifier: String, completionHandler: (SFSafariExtensionState?, Error?) -> Void) Which allows us to inform the user in the container app if the extension is currently enabled. This API is not available in iOS. Is there a technical reason or policy behind this or is it just a missing feature in which case I will raise a feature request?
Posted Last updated
.
Post not yet marked as solved
0 Replies
80 Views
I am trying to create an app that downloads files and stores these files in the document folder that can be seen inside the Files App. I added these to the Info.plist file <key>UIFileSharingEnabled</key> <true/> <key>LSSupportsOpeningDocumentsInPlace</key> <true/> I also added debugging code to test that the file is indeed inside the document folder. The code can see the file, but the Files app doesn't show my app document's folder by itself (not even after adding files manually). There is a special case that does show my app folder inside the Files app: when I move an existing file I have to my app document folder. That is the only way that the Files app shows my app document folder - but the strange part is that still it doesn't show the file I downloaded inside (only the file I moved manually). Also: I tested on the simulator using iPhone 12 Pro and on a real device: same result. I deleted and re-installed the app many times, and also restarted Xcode and restarted the real device - nothing changed. This is the code that downloads the file into my app document folder. func downloadFile(urlString: String, filename: String, completionHandler: @escaping (_ err: Error?) -> Void) {     let config = URLSessionConfiguration.default           guard let url = URL(string: urlString), let documentPathURL = getDestFileURL() else { return }           let request = URLRequest(url: url)     let session = URLSession(configuration: config)           os_log("getting download folder %{public}@", documentPathURL.path as CVarArg)     let fileManager = FileManager()     let task = session.downloadTask(with: request) { url, response, error in       if error != nil {         os_log("error: %{public}@", error! as CVarArg)         completionHandler(error)         return       }       guard let fileURL = url else { return }       let fileNameParts = filename.components(separatedBy: ".")        do {         var isDir:ObjCBool = true         // download folder exists?         if !FileManager.default.fileExists(atPath: documentPathURL.path, isDirectory: &isDir) {           os_log("creating new folder")           try FileManager.default.createDirectory(atPath: documentPathURL.path, withIntermediateDirectories: true, attributes: nil)         }         let savePathURL = documentPathURL.appendingPathComponent(fileNameParts[0]).appendingPathExtension(fileNameParts[1])         // dest file exists?         if FileManager.default.fileExists(atPath: savePathURL.path) {           os_log("removing existing file")           try FileManager.default.removeItem(atPath: savePathURL.path)         }         // all good? then move the file!         try fileManager.moveItem(at: fileURL, to: savePathURL)         os_log("from path %{public}@", fileURL.path)         os_log("to path: %{public}@", savePathURL.path)         // dest file exists?         if FileManager.default.fileExists(atPath: savePathURL.path) {           let files = try fileManager.contentsOfDirectory(atPath: documentPathURL.path)           try fileManager.setAttributes([FileAttributeKey.protectionKey : FileProtectionType.none, FileAttributeKey.posixPermissions: 0o777], ofItemAtPath: savePathURL.path)           let attrs = try fileManager.attributesOfItem(atPath: savePathURL.path)           os_log("list of files %{public}@", files as CVarArg)           os_log("attrs of file %{public}@", attrs as CVarArg)           os_log("move was a success")         }                   completionHandler(nil)       }       catch {         os_log("final error: %{public}@", error as CVarArg)         completionHandler(MyError.couldNotDownload)       } } Can anyone see something wrong or missing? Thanks
Posted
by pbrea.
Last updated
.
Post not yet marked as solved
2 Replies
211 Views
In the manifest.json file I have set the following: "web_accessible_resources": [ "template.html" ] Then used the following command to get the url browser.runtime.getURL('template.html') Which results in the following as expected: safari-web-extension://40C1D308-F374-4C87-98C8-D9E9A4AD7945/template.html However, when trying to fetch the file from within the script I get : Failed to load resource: The requested URL was not found on this server. And when pasting it as a url in safari I get : Safari cannot open the page because the address is invalid. The template.html file exists in the same folder as the manifest.json file. I've even tried placing it in a folder called html, then using the following with wildcard, and that doesn't work either. "web_accessible_resources": [ "html/*.html" ]
Posted
by SuavePlan.
Last updated
.
Post not yet marked as solved
1 Replies
114 Views
I'm tasked with securing a Safari browser extension that my organization created. It is using WebExtensions and has been launched across various browser extension stores, including Safari. I've searched for many hours on this and find very very little information on how to secure it. (Pretty much just https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy.) Is there anything more extensive/comprehensive? I'll be dealing with Safari Desktop as well as Safari iOS. By comparison, when looking for resources for securing the Firefox version of the extension, I found https://extensionworkshop.com/documentation/develop/build-a-secure-extension/, which I think is a great resource. Some of that content applies to Safari, but it's hard to know to what extent. I'll need to secure Safari web and Safari mobile (i.e. iOS), but for now I'm focused on web. (But if you have anything on mobile, then please let me know also.) Thank you!
Posted
by Alright.
Last updated
.
Post not yet marked as solved
1 Replies
130 Views
I am building a safari extension app. the code(html, css, javascript) for the safari extension that is in the AppExtension directory works good, but when I added button to Main.html to make an Action, to make the app dynamically change (the application itself not the extension), and I want to send data from the app to the extension on Safari. Could anyone help me to fix this issue. This my html code: <!--Main.html --> <!DOCTYPE html> <html> <head>     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">     <meta http-equiv="Content-Security-Policy" content="default-src 'self'">     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">     <link rel="stylesheet" href="../Style.css">     <script src="../Script.js" defer></script> </head> <body> <!--The makeAction() function is defined in the `Script.js` file -->         <button onclick="makeAction()">Dropdown</button> </body> </html> And this is the files structure
Posted
by osos.
Last updated
.
Post not yet marked as solved
1 Replies
299 Views
I'm not sure what is special about Gmail, but my declarativeNetRequest rules are totally ignored. I make an web extension that blocks email trackers (1x1 pixel images embedded in emails to track if and when you open email sent to you). All images in Gmail are loaded through Google's proxy: googleusercontent.com/proxy/#originalURL But no matter what I do, I can't block a single image that is loaded in an email. To try and prove it is a bug in Safari, I created a new template web extension in Xcode. I block all resourceTypes (images and other should be all that is needed) and added two rules: Block all images loaded through Google's proxy server (this should block all embedded images in all emails) Block any image with copper in the URL (just in case the blocking doesn't apply to the proxy root url for some reason).  {   "id": 1,   "priority": 1,   "action": { "type": "block" },   "isUrlFilterCaseSensitive": false,   "condition": {    "regexFilter": "googleusercontent.com/proxy",    "resourceTypes": [     "image",     "media",     "main_frame",     "sub_frame",     "stylesheet",     "script",     "font",     "xmlhttprequest",     "ping",     "websocket",     "other"    ]   }  },  {   "id": 2,   "priority": 1,   "action": { "type": "block" },   "isUrlFilterCaseSensitive": false,   "condition": {    "regexFilter": "copper",    "resourceTypes": [     "image",     "media",     "main_frame",     "sub_frame",     "stylesheet",     "script",     "font",     "xmlhttprequest",     "ping",     "websocket",     "other"    ]   }  } ] Even though I know this isn't needed, I also added the requester domain (mail.google.com) and the proxy domain (googleusercontent.com) to the permissions list in the manifest file: ... "declarative_net_request": {    "rule_resources": [{      "id": "ruleset_1",      "enabled": true,      "path": "rules.json"     }]   },  "permissions": [    "declarativeNetRequest",    "*://mail.google.com/*",   "*://*.googleusercontent.com/proxy/*" ] If I open an email from copper, the image still loads in Gmail If I right click and select "Open Image in New Tab", the image will not load and I am told it was blocked If I open a test page that has two images in it, one the image from the email, the image is blocked and the other image is not (as expected, the second image is from wikipedia and should not be blocked) Running the same extension in Chrome DOES block the image in Gmail (and in all the other cases too)
Posted
by leggett.
Last updated
.
Post not yet marked as solved
6 Replies
1.5k Views
I'm trying to use browser.storage.onChanged - https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/onChanged to detect when changes are made to browser.storage.local where I save all the preferences for my Safari Web Extension. This works on Chrome, Firefox, Edge, and Opera. But I can't get it to work on Safari. When I call browser.storage.local.set, the onChanged listener is not called. Below is the code I'm using to test this in Safari. This has to be in an extension with the Storage permission. - https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions Given this works in all other browsers and I'm following the documentation, I'm inclined to believe this is a bug in Safari. Am I missing something? /* Function to handle changes to storage */ function handleChange(changes) { &#9;for (var key in changes) { &#9;&#9;var storageChange = changes[key]; &#9;&#9;console.log('onChanged triggered. '+ &#9;&#9;&#9;&#9; 'Storage key "%s" changed. ' + &#9;&#9;&#9;&#9; 'Old value was "%s", new value is "%s".', &#9;&#9;&#9;&#9; key, &#9;&#9;&#9;&#9; storageChange.oldValue, &#9;&#9;&#9;&#9; storageChange.newValue); &#9;} } /* Add listener for storage changing */ browser.storage.onChanged.addListener(handleChange) /* Confirm that the listener has been attached (expect "true" to be returned)*/ browser.storage.onChanged.hasListener(handleChange) /* Set value for ['testKey] (should trigger onChanged event which calls handleChange) */ browser.storage.local.set({testKey: true}, () => { console.log('Storage updated'); }); /* Get current value for ['testKey] in storage.local, should return {testKey: true}*/ browser.storage.local.get(['testKey'], (result) => { console.log(result); }); /* Change value for ['testKey] (should trigger onChanged event which calls handleChange) */ browser.storage.local.set({testKey: false}, () => { console.log('Storage updated'); });
Posted
by leggett.
Last updated
.
Post not yet marked as solved
2 Replies
136 Views
Hi all, I have a stand-alone Chrome extension app, and used the xcrun converter last year to create a desktop Safari app, and used the xcrun converter again to create a mobile safari extension. Now I want to bundle that extension into an existing iOS app. However I also want the ability to update the extension as if it were a cocoapod or a swift package. Is this possible?
Posted
by Gettediah.
Last updated
.
Post not yet marked as solved
5 Replies
752 Views
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) => { &#9;&#9;console.log("Received response: ", response); }); // NEVER FIRES browser.runtime.onMessage.addListener((request, sender, sendResponse) => { &#9;&#9;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?
Posted Last updated
.
Post not yet marked as solved
1 Replies
193 Views
I’m developing safari web extension on macOS using Xcode. I need to send message from web extension’s background.js to containing APP. As “Messaging a Web Extension’s Native App” describe, I create a port in background.js as follow: let port = browser.runtime.connectNative("application.id"); In background.js send message as follow: port.postMessage("Hello from JavaScript Port"); how can I receive this message in containing APP? I would be very grateful if you could give an example. Thanks! Another problem is how can connectNative start the containg APP on macOS. As runtime.connectNative describe in MDN(https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connectNative) “It starts the native application and returns a runtime.Port object to the caller.The caller can then use the Port to exchange messages with the native application using Port.postMessage() and port.onMessage” In safari, connectNative seems cannot start the containing APP. How can I make it start the containing APP?
Posted Last updated
.
Post not yet marked as solved
1 Replies
146 Views
Hello, How can I change the commands originally set in the manifest? Is there a special internal url like in chrome since browser.commands.update does not seem to exist in Safari. Thank you
Posted
by bruno.cm.
Last updated
.
Post not yet marked as solved
2 Replies
235 Views
I made a Safari Web extension that's supposed to work like this: WHEN: Safari is opening http://example.com/ THEN: The extension opens a page embedded in the extension instead. This is the implementation of background.js: browser.tabs.onUpdated.addListener(async function (tabId) { const tab = await browser.tabs.get(tabId); if (tab.url == "http://example.com/") { const destination = browser.runtime.getURL("embedded-page.html"); browser.tabs.update(tabId, { url: destination }); } }, null); This is the source project: https://www.icloud.com/iclouddrive/042qIjivEoJ0V3qIcLGcytAPA When opening http://www.example.com/ for the first time, it successfully navigates to the embedded page. However, once that navigation is done, the extension no longer works that way unless I re-enable the extension. It's like the listeners are removed by that navigation. This is the video of this issue: https://www.icloud.com/iclouddrive/0f9Yl1jC9eQ8OmgH8vAtEA6Pw Can you replicate this on your Mac? And do you know what's the cause? This happens for a listener of browser.webNavigation.onBeforeNavigate too. Maybe more. On the other hand, when I set a normal page such as https://www.apple.com/ to destination, It always works fine for me. So I'm wondering if this issue is related to the embedded page. FYI, I already reported this issue as FB9967637 on 27th March but no reply from Apple as of now. My environment macOS Monterey 12.3.1 (21E258) Safari 15.4
Posted
by normal.
Last updated
.
Post not yet marked as solved
1 Replies
168 Views
Hi, I'm trying to convert my chrome extension to work in Safari. I've got it mostly working but there is one issue with the webRequest.onResponseStarted.addListener api. The callback is missing fields including ip. Here is an example: webRequest.onResponseStarted.addListener((r) => { console.log(r); }, {"urls": ["<all_urls>"]) In chrome this will print all the fields described in the spec. But in safari it is missing a lot of fields, including ip, type, etc. Does anyone know if this is a bug or if I am doing something wrong? Thanks!
Posted
by mjbford89.
Last updated
.
Post not yet marked as solved
1 Replies
137 Views
I am creating a Safari Web Extension to add some security features to Safari browser. Web Extension has 2 parts, 1st - script code, which loads in browser and 2nd - a separate application/process called Native App which gets notifications and data of browser activities. I am able to get notifications of various browser activities in Safari Native App. When such notification is received, this needs to be sent to other daemon process running on same MacOS machine over unix domain socket (used for local IPC). This Safari Native App is running within a sandbox. The issue is, when trying to connect to already listening socket of other process from Safari Native App, the sandbox of Native App denies outbound socket network connection. Question: Is there a way to communicate from sandboxed Native App using socket with other processes. Disclaimers: Sandbox cannot be disabled, if disabled it stops getting notifications from Safari for activities, which is its basic work. Entitlements and app groups addition can be done, but the process to which it has to communicate cannot be added to app group. Please help/suggest what can be way out from this problem.
Posted Last updated
.