Convert Web Extension to Safari App Extension
L

8

11

I have Web Extensions which currently runs on Chrome, Firefox and Opera. Now I'm wondering is there a way to use same code to build Safari App Extension, maybe something like PhoneGap(wrap all existing JS code in Safari App Extension project) or there are limitations like tabs handling for javascript and some things just have to be written in native code.

Thanks

Lodge answered 24/9, 2018 at 8:18 Comment(0)
P
13

You should be able to reuse Content Scripts from your Web-Extensions (but you need to rewrite code to be able to send/receive messages with "background"). Background script will have to be re-written in Swift or Objective-C.

You need to developp:

  • a Mac Host Application (which will be a companion to the Extension) : Swift or Objective-C (this app must have minimum features to pass Apple review - check the Apple guidelines for Mac Apps)
  • the extension : in Swift or Objective-C + HTML/JS (as for other browsers) The code in Swift (or Obj-C) for the extension is the equivalent of the background page you have in Web Extensions. It will control the toolbar button, life-cycle of the extension and can talk with injected scripts.

You have an (small) example on Apple website : https://developer.apple.com/documentation/safariservices/safari_app_extensions?changes=_2&language=objc

You should not use Extension Builder anymore (accessible from Developer menu in Safari) since this is for legacy (pure JS) extensions only. So you need to get in XCode.

Last thing, but not least... Safari App Extensions, compared to Safari legacy extensions, are "a bit" more limited (for instance, you won’t be able to open the toolbar popup programmatically, just as with Chrome web-extensions actually).

Pragmatics answered 28/9, 2018 at 12:15 Comment(5)
I have created a Slack community for Safari App Extensions devs, everyone is welcome to join us: slofile.com/slack/safariappextdevsPragmatics
A few updates on this. Regarding the app review, you could instead distribute the Mac app yourself and avoid some of those requirements. Safari App Extensions is still under development and there are new APIs being added. So keep your eye on the release notes. I think, for example, since this answer was written, that there is now a way to open the popover programmatically. Some things still remain though, it is still very restrictive and docs and examples are seriously lackingPham
Quin is right, we can open the popup programmatically (since 10.14.2 if I remember well). But, still, the API is not as rich as for WebExtensions.Pragmatics
This is also an outdated information, don't follow this.Bun
Outdated... for last version of Safari, since it supports Web Extensions. But, still valid if you need to support older versions or if you already have this kind of extension (and don't want to "come back" to chrome extensions).Pragmatics
S
9

Now there is an official support for this converting:

Converting a Web Extension for Safari

Seanseana answered 21/7, 2020 at 8:41 Comment(1)
Ok, we just wrapped up a blog for this, in case that helps some others: bartsolutions.medium.com/…Kaela
T
7

May be it too late to share my finding, but nevertheless.

Short answer for the possibility of converting web extension to safari app extension is Yes. It definitely possible, But it's hard to do so.

I had a hard time Figuring out the best possible and minimal way to port web extension to safari app-extension.

Problems with porting Web Extension

  1. Safari App-Extension doesn't offer API for tab/window management. (i.e when a tab or window is added, removed or update, unique Identifier for windows and tab)
  2. Require some learning or prior experience with swift/objective -c to implementation background functionality.
  3. Debugging becomes a pain point during development.

Solution

Writing some amount of swift/objective-c code is inevitable. But writing a bridge code in swift/objective C to facilitate the communication between content-script and background-script makes life easier.

In the diagram, Content-script remains the same, the background-script is injected by creating a webview. Now Any message received from content-script is forward to the webView background-script. Also setup the browser extension API using evaluvateJavascript

webView.evaluateJavaScript("chrome.runtime.id=function() { return "+ extensionId + " }")
  • Approach 2 : Using Electron powered browser extension

Electron JS uses web technologies like simple HTML, CSS, and JavaScript. It does not require native skills unless you want to do something advanced. It can be designed for a single browser. Its file system belongs to Node.js APIs and works on Linux, Mac OS X, Windows.

By using native node modules enables you to write in C++ and ObjectiveC (or Swift) and expose an API to node.js using v8. This gives you a lot of flexibility and power but requires the most time to develop and still being to reuse you background-script by running in electron context.

Sample Application built using this approach https://github.com/AdguardTeam/AdGuardForSafari

If you want to do Window/Tab Management in safari app extension refer WindowTabManagement

UPDATE

If you are planning to port your legacy extension with zero native code (Objective-c/swift). Please follow this repo https://github.com/avast/topee

Torpedoman answered 8/2, 2020 at 17:40 Comment(5)
Can you tell why do you spread false information and doing that on Feb '20 when your info is probably outdated? For "tab/window management" - it works pretty well. I noticed only one difference in the small extension - Safari cannot recognize browser.tabs.sendMessage messages if you try sending them to the local urls (safari-web-extension://). Other browsers work well with that. Besides this, my app is able to get most of the data out of windows/tabs. Ids, titles, etc. It cannot get favIconUrls which is stated in the docs (compatibility table). I don't know why you got upvotes on this.Bun
This is basically a big piece of very false and outdated information, you can easily port the WebExtension (even just the Chrome one) with the XCode 12+.Bun
To my first comment, you have to use browser.runtime.sendMessage, that will only be catched in Safari local url windows.Bun
@Bun ehm, what? what do you mean by false information here? The safari app extension works completely differently compared to other browser extension. The background script itself is fully written in Native. 1. Safari app extension doesn't have any tab management functionality l.e Query tab, getTabUsingId [developer.apple.com/documentation/safariservices/… here).Torpedoman
Safari supports only pages, In other extensions, we can query tab where each can tab contain multiple pages(iframes) and there is no way in Safari app extension you can retrieve the using tab id. Those these are outdated when safari, later on, agreed to support "safari web extension" which is different from "Safari app extension" in the WWDC20 event (on 22 Jun 2020 – 26 Jun 2020). The question is especially about the safari app extension, I would argue to read the question carefully.Torpedoman
N
2

You can actually do this fairly easy.

  • Keep in mind you need Xcode v.12 and Safari 12 for this to work.
  1. Download the .crx file from the chrome store.
    1. I used this: https://crxextractor.com
  2. Unzip with terminal «unzip».
  3. From terminal type xcrun safari-web-extension-converter /path/to/manifest/ and run it - enter yes when asked if swift. This should open Xcode.
  4. From Xcode - Compile and run.
  5. Now to activate it:
    1. Go to safari, preferences, enable dev mode.
    2. Then go to the new «develop» tab - next to bookmarks.
    3. «Allow Unsigned Extensions»
  6. Go then to the preferences and extensions, and you should see your extension.
Nickolai answered 13/11, 2020 at 9:42 Comment(0)
F
0

You may refer with this link on how to convert Google Chrome Extension to Firefox or Safari extension.

There is currently a bit of manual work to be done.

https://github.com/kritollm/chrome-extension-api-for-safari-and-firefox

After I first wrote this post, I have been aware of two other projects which are very similar.

https://code.google.com/p/adblockforchrome/source/browse/trunk/port.js and

https://github.com/jetpack-labs/chrome-tailor-jetpack

Also, here's the official guide to how to convert Chrome extensions to Firefox add-on using WebExtensions.

Foreplay answered 24/9, 2018 at 16:0 Comment(1)
Yes, those are tools for converting Chrome Extension to Safari Gallery Extension. But Apple announced that Safari Gallery Extensions are deprecated and now they will support only Safari App Extensions. links: developer.apple.com/documentation/safariservices/… 9to5mac.com/2018/06/09/safari-12-extensions-moreLodge
D
0

If we're talking about a Safari App extension and not a legacy safari extension (deprecated in Safari v12), then my understanding is that the background script needs to be rewritten in swift. Regarding the content scripts, they are still in JS but all the messaging written with chrome API calls must be rewritten using Safari app APIs.

Divorcement answered 25/9, 2018 at 9:11 Comment(0)
K
0

Not all things can be written in Swift. For example, I've tried call dylib from extensionHandler, but was failed.

Kinetics answered 5/10, 2018 at 10:34 Comment(0)
A
0

As per above, the content scripts can remain the same as the bundles you create in your Firefox and Chrome extensions. You will just need to include it in your plist. To avoid rewriting all the background logic in Swift, I used a library called JavascriptCore provided by Apple. This allows you to create a JavaScript context and pass data from the Swift background to your JavaScript background bundle.

For example: safariExtensionHandler.swift has a function called messageReceived. This function can call a global JavaScript function in your code from Swift using JavascriptCore.

Arand answered 28/8, 2019 at 7:56 Comment(2)
Do you have a localStorage and/or IndexedDB in JavaScriptCore environment? How do you make AJAX requests? Are CORS requests possible?Ptolemaic
@Ptolemaic Concerning the localStorage: 1- In my Safari App Extension javascript wrapper, every call to store data in the localStorage actually calls a global function, global.setInKeychain(data). 2- global.setInKeychain(_ data:JSValue) is implemented from the Swift side and then injected in my Javascript Context. I implemented storing/getting/deleting data from the Keychain. Correct me if I am wrong, For CORS, I believe that simply whitelisting the urls in your plist and setting the src attribute of the <img> tag to baseURI + url will let you display images on webpages.Arand

© 2022 - 2024 — McMap. All rights reserved.