背景由于常用浏览器是Safari,而 Safari 浏览器的插件比不上 Chrome,所以就有了自己开发常用的 Safari 插件的想法。

打算开发当前页面生成二维码的 Extension,因为网络原因,AirDrop 有时候搜不到手机,所以有了这个需求,而且打算这个也比较简单,所以从这个开始。

调研苹果的官方文档safari_web_extensions给出了步骤,看了好几遍,还是不知道如何下手。虽然新建项目的时候苹果帮忙把框架已经都建好了,但是还是有疑惑,疑惑的点在于:

主APP的作用是什么?

Extension的作用是什么?

代码应该写在哪里?

manifest.json支持的配置项有哪些,哪里可以看到,应该设置哪些?

content.js、background.js分别是干什么用的,什么时候用?

popup.html、popup.css、popup.js 又是指的哪部分?

content.js、background.js、popup.js中操作 tab 的方法有哪些?哪里可以看到?

这些疑惑一度导致开发计划搁浅,因为没有文章来解释这些问题。于是转而去 Github 上找有没有SafariExtension 的代码,看其他人是如何实现的?搜到了ADGuardForSafari、userscripts等等很优秀的 Extension,但是项目太大了,对于还没入门的笔者来说,解答不了上面的疑惑。

解决直到偶然看到QR Code Extension这个,下载对比官方文档各个文件的解释,笔者终于对上面的疑惑有了答案,终于能动手开发自己的 Extension 了。

主APP的作用是定义 Extension 的设置(配置项),如果开发简单的 Extension,比如二维码生成、json格式化等不需要自定义设置的,主APP不需要修改。

Extension的是实现插件的实际地方,代码应该写在这里,这里面的代码是 html 和 js 的内容和原生开发没太大关系。

manifest.json支持的配置项可以看Assessing your Safari web extension’s browser compatibility 和 Browser compatibility for manifest.json,有初步印象即可,然后可以参照其他项目,再根据实际情况决定 manifest.json 里设置哪些。

content.js可以理解为注入到当前打开Tab页面的js,background.js则是Tab不活跃时的,如果不涉及不活跃Tab,则background.js中无需实现。

popup.html、popup.css、popup.js 指的是点击 Extension 按钮时,出现的下拉界面。

操作 tab 的方法可以参考Manage tabs,把里面 chrome.tabs 改为 browser.tabs来调用即可。

手把手开发一个当前页面链接生成二维码的插件选中 Xcode -> File -> New -> Project, 然后选中 Multiplaform -> Safari Extension App,选中 Multiplaform代表同时支持iPhone和Mac,如下图:

下一步,输入项目名称,选择语言,如下图:

然后选择存储地方,保存,项目会自动打开,结构如下,可以看出,分为几个部分:

Shared (App)

ViewController.swift:Mac/iOS APP的主界面,其中是一个 webview 加载 Resources 下的 Main.html;这个类可以不修改;如果有从 APP 中自定义Extension设置选项的功能,则需要修改。

Assests.xcassets:Mac/iOS APP 的图标,可以用AppIconGenerator来一键生成。Ps: 开发了两个ExtensionAPP,生成图标有点麻烦,所以干脆开发了一个,欢迎使用。

Resources

Main.html

Icon.png

Style.css

Script.js

Shared (Extension)

_locales

images

manifest.json

background.js

content.js

popup.html

popup.css

popup.js

iOS (App)

macOS (App)

iOS (Extension)

macOS (Extension)

再来考虑一下要做的界面: 初步设想是一个最简单的,点击 Tab 栏的 Icon,展开生成一个当前页面链接的二维码。

然后来看下如何实现:

首先配置 manifest.json 中的设置项,因为插件的功能是对所有网页都生效,所以修改content_scripts中的 matches为所有网页,且配置host_permissions所有网页;另外需要获取当前活跃的Tab,所以在permissions中添加配置,最终要修改的配置项如下:

1234567891011121314{ ... "content_scripts": [{ "js": [ "content.js" ], "matches": [ "http://*/*", "https://*/*", "" ] }], "host_permissions": ["http://*/*", "https://*/*"], "permissions": [ "activeTab", "tabs", "scripting", "messaging" ]}

然后来考虑界面,用接口生成二维码图片然后加载显示的方式,需要有一个 loading,然后一个放置图片的地方;所以在popup.html中修改如下:

1234567891011121314

QR Code

然后popup.css中设置 loading 动画和界面布局,代码如下:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950:root { color-scheme: light dark;}body { width: 360px; height: 360px; font-family: system-ui; text-align: center; margin: 0; background-color: white;}#loader { position: absolute; left: 50%; top: 50%; z-index: 1; width: 120px; height: 120px; margin: -76px 0 0 -76px; border: 16px solid #f3f3f3; border-radius: 50%; border-top: 16px solid #3498db; -webkit-animation: spin 2s linear infinite; animation: spin 2s linear infinite;}@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); }}#img_back { display: block; position: absolute; left: 50%; top: 50%; z-index: 1; width: 320px; height: 320px; margin: -160px 0 0 -160px;}#qrcode { width: 100%; border-radius: 9px;}

然后来考虑 js 的实现,点击 Tab 栏的Icon,直接触发生成二维码的方法,具体步骤如下:

查询所有 Tab

获取正在显示的 Tab

获取正在显示 Tab 的链接

用链接发起生成二维码的请求,然后显示在 img 上

这里需要注意的是第三步,获取正在显示的 Tab 的链接,需要在 content.js 中获取,所以就需要通过方法通信,触发 content.js 获取当前的链接,然后再从 content.js 中回传给 popup.js 中,因为最终显示是在 popup.html 中,所以需要通过 popup.js 来发起请求。popup.js中代码如下:

12345678910111213141516171819202122232425262728const kQRAPI = "https://qrcode.tec-it.com/API/QRCode?data="function generateQRCode(methodName, message) { // 查询所有 Tab browser.tabs.query({ active: true, currentWindow: true }, function (tabs) { // 获取当前正显示的 Tab var activeTab = tabs[0]; // 发消息给 content.js,告诉它获取当前链接 browser.tabs.sendMessage(activeTab.id, { title: methodName, message: message}, function (res) { // content.js 获取后回调到这里 if (res.title == "targetURL") { const activeTabURL = res.urlStr; const encodedTabURL = encodeURIComponent(activeTabURL); // 获取popup.html 中 img var qrcodeImg = document.getElementById("qrcode"); qrcodeImg.onload = function() { // 图片加载完成,loading 消失 document.getElementById("loader").style.display = "none"; }; // 通过请求获取二维码照片 qrcodeImg.src = kQRAPI + encodedTabURL + "&istransparent=true"; } }); });}// 直接触发生成二维码的方法generateQRCode("getPageURL", "generate current page URL");

content.js中代码如下:

1234567browser.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log("Received request: ", request); // 接收 popup.js 中发送的消息,并回调结果 if (request.title == "getPageURL") { sendResponse({ title: "targetURL", urlStr: window.location.href }); }});

然后选择 macOS 运行,如下图

效果如下:

然后来考虑优化,通过请求生成二维码依赖网络环境,如果网络环境不好,可能 loading 时间过长,甚至失败,那么能不能不通过请求,直接生成二维码?

答案是可以的,参考Chrome上的QR Code Generate,点击后马上就生成二维码,如下:

所以是可以优化的,通过 js 直接生成二维码,而不需要依赖网络。最终效果如下:

插件已上架到商店,名字为[GenerateQR-Extension],欢迎体验。希望大家通过上面的介绍都能开发自己常用的Safari-Extension。

参考

safari_web_extensions

chrome_web_exrensions

Assessing your Safari web extension’s browser compatibility

Browser compatibility for manifest.json

QR Code Extension

LSApplicationCategoryType

谢谢,一起进步

支付宝

微信