3

如何开发一个 Safari 插件

 11 months ago
source link: https://morganwang.cn/2023/06/07/%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AASafari%E6%8F%92%E4%BB%B6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何开发一个 Safari 插件

2023-06-07

由于常用浏览器是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 的代码,看其他人是如何实现的?搜到了ADGuardForSafariuserscripts等等很优秀的 Extension,但是项目太大了,对于还没入门的笔者来说,解答不了上面的疑惑。

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

  • 主APP的作用是定义 Extension 的设置(配置项),如果开发简单的 Extension,比如二维码生成、json格式化等不需要自定义设置的,主APP不需要修改。
  • Extension的是实现插件的实际地方,代码应该写在这里,这里面的代码是 html 和 js 的内容和原生开发没太大关系。
  • manifest.json支持的配置项可以看Assessing your Safari web extension’s browser compatibilityBrowser 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,如下图:

screenshot-20230628-171721.png

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

screenshot-20230628-172143.png

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

  • 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)
screenshot-20230628-185816.png

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

然后来看下如何实现:

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

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

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

<!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 动画和界面布局,代码如下:

: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,直接触发生成二维码的方法,具体步骤如下:

  1. 查询所有 Tab
  2. 获取正在显示的 Tab
  3. 获取正在显示 Tab 的链接
  4. 用链接发起生成二维码的请求,然后显示在 img 上

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

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中代码如下:

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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK