本文字数:2493字
预计阅读时间:15分钟
由于常用浏览器是Safari,而Safari浏览器的插件比不上Chrome,所以就有了自己开发常用的Safari插件的想法。
打算开发当前页面生成二维码的Extension,因为网络原因,AirDrop有时候搜不到手机,所以有了这个需求,而且这个也比较简单,所以从这个开始。
01
调研
苹果的官方文档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 等等很优秀的Extension,但是项目太大了,对于还没入门的笔者来说,解答不了上面的疑惑。
02
解决
直到偶然看到 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来调用即可。
03
手把手开发一个当前页面链接生成二维码的插件
选中Xcode -> File -> New -> Project, 然后选中Multiplaform -> Safari Extension App,选中Multiplaform代表同时支持iPhone和Mac,如下图:
下一步,输入项目名称,选择语言,如下图:
然后选择存储地方,保存,项目会自动打开,结构如下,可以看出,分为几个部分:
-
Shared (App)
-
Main.html
-
Icon.png
-
Style.css
-
Script.js
-
ViewController.swift:Mac/iOS APP的主界面,其中是一个webview 加载Resources下的Main.html;这个类可以不修改;如果有从APP中自定义Extension设置选项的功能,则需要修改;
-
Assests.xcassets:Mac/iOS APP的图标,可以用AppIconGenerator 来一键生成(Ps: 开发了两个ExtensionAPP,生成图标有点麻烦,所以干脆开发了一个,欢迎使用);
-
Resources
-
-
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中添加配置,最终要修改的配置项如下:
go
{
...
"content_scripts": [{
"js": [ "content.js" ],
"matches": [ "http://*/*", "https://*/*", "<all_urls>" ]
}],
"host_permissions": ["http://*/*", "https://*/*"],
"permissions": [
"activeTab",
"tabs",
"scripting",
"messaging"
]
}
然后来考虑界面,用接口生成二维码图片然后加载显示的方式,需要有一个 loading,然后一个放置图片的地方;所以在popup.html中修改如下:
go
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="popup.css">
<script type="module" src="popup.js"></script>
</head>
<body>
<div id="loader"></div>
<div id="img_back">
<img id="qrcode" src=" " alt="QR Code" />
</div>
</body>
</html>
然后popup.css中设置loading动画和界面布局,代码如下:
go
: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中代码如下:
go
const 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中代码如下:
go
browser.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运行,如下图:
选择 macOS
效果如下:
loading生成二维码
然后来考虑优化,通过请求生成二维码依赖网络环境,如果网络环境不好,可能loading时间过长,甚至失败,那么能不能不通过请求,直接生成二维码?
答案是可以的,参考Chrome上的QR Code Generate,点击后马上就生成二维码,如下:
Chrome QR Code Generate
所以是可以优化的,通过js直接生成二维码,而不需要依赖网络。最终效果如下:
GenerateQR-Extension
插件已上架到商店,名字为 [GenerateQR-Extension],欢迎体验。希望大家通过上面的介绍都能开发自己常用的 Safari-Extension。
参考:
-
safari_web_extensions(https://developer.apple.com/documentation/safariservices/safari_web_extensions)
-
chrome_web_exrensions(https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/)
-
Assessing your Safari web extension's browser compatibility ( https://developer.apple.com/documentation/safariservices/safari_web_extensions/assessing_your_safari_web_extension_s_browser_compatibilit)
-
Browser compatibility for manifest.json
-
QR Code Extension
-
LSApplicationCategoryType ( https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype)