如何开发一个 Safari 插件

news/2025/3/15 5:15:39/

078d8c125c216a46bd095517b69202bc.jpeg

aac3518b2e65bd64a923bcf5c6df156d.gif

本文字数:2493

预计阅读时间:15分钟

88d342aaa21f1597b023cda801fc4913.png

由于常用浏览器是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,如下图:

d4dc105e517da2b84511ffb22df18ac0.png

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

d731756d8feee3da2397cecde1fbe491.png

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

  • 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)

f353c1a8be922535e10d232de31e9694.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) {// 查询所有 Tabbrowser.tabs.query({ active: true, currentWindow: true }, function (tabs) {// 获取当前正显示的 Tabvar 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 中 imgvar 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运行,如下图:

79d1a31b5a0a4be330778da88280bd20.png
选择 macOS

效果如下:

cee12b8d4888431b26f9fb1409d2c3d7.gif

loading生成二维码

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

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

924c1be5b9d0050e5a780f174d448188.gif

Chrome QR Code Generate

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

1ac62a0f70bb47c911eee53ae4e6eeeb.gif

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 compatibilityhttps://developer.apple.com/documentation/safariservices/safari_web_extensions/assessing_your_safari_web_extension_s_browser_compatibilit)

  • Browser compatibility for manifest.json

    (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_compatibility_for_manifest.json)

  • QR Code Extension

    (https://github.com/NiklasGabriel/QR-Code)

  • LSApplicationCategoryTypehttps://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype)


http://www.ppmy.cn/news/1171342.html

相关文章

Pandas数据处理分析系列6-数据特征分析

Pandas 数据特征分析 在前面章节学习了Pandas 两种数据结构Series、DataFrame,及Pandas如何读取Excel数据格式文件,数据预览、数据清洗及数据提取,接下来了解这些数据征分析。在实际工作中,财务、金融、制造业等数据,需从不同维度的指标进行分析,如合计数、最大数、最小数…

塔望3W消费战略全案丨九代拉祜人,一饼古树茶

存木香 客户&#xff1a;云南双江存木香茶叶商贸有限公司 品牌&#xff1a;存木香 时间&#xff1a;2019年 &#xff08;项目部分内容保密期&#xff09; 沧海桑田 存木香依然 存木香 CUNMUXIANG( 全称云南双江存木香茶业有限公司 ), 成立于2011 年 , 总部设于北回归线横…

Java-IO流

文章目录 Java-IO流文件字节流文件字符流File类缓冲流转换流打印流数据流对象流 Java-IO流 JDK提供了一套用于IO操作的框架&#xff0c;为了方便我们开发者使用&#xff0c;就定义了一个像水流一样&#xff0c;根据流的传输方向和读取单位&#xff0c;分为字节流InputStream和…

Python学习--函数的参数

三、函数的参数 1、函数的参数类型 设置与传递参数是函数的重点&#xff0c;而 Python 的函数对参数的支持非常的灵活。 主要的参数类型有&#xff1a;默认参数、关键字参数&#xff08;位置参数&#xff09;、不定长参数。 下面我们将一一了解这几种参数。 2、默认参数 …

买充电桩你必须要知道三件事

分享一套开源充电桩云平台&#xff08;v2.5.1&#xff09;-- 支持二轮(电动自行车)、四轮(电动汽车) 中国电动汽车充电基础设施促进联盟的信息部主任&#xff0c;仝宗旗&#xff0c;在2022年接受了《中国汽车报》采访的时候&#xff0c;提到过一个数据。 电动车主在小区成功安…

Flutter的Don‘t use ‘BuildContext‘s across async gaps警告解决方法

文章目录 问题有问题的源码 问题原因问题分析Context的含义BuildContext的作用特殊情况 解决方法 问题 Flutter开发中遇到Don’t use BuildContext’s across async gaps警告 有问题的源码 if (await databaseHelper.isDataExist(task.title)) {showDialog(context: context,…

C++前缀和算法:合并石头的最低成本原理、源码及测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 动态规划&#xff0c;日后完成。 题目 有 n 堆石头排成一排&#xff0c;第 i 堆中有 stones[i] 块石头。 每次 移动 需要将 连续的 k 堆石头合并为一堆&#xff0c;而…

重组件的优化和页面渲染十万条数据

重组件的优化和页面渲染十万条数据的优化 重组件的优化vue2写法vue3写法 页面渲染十万条数据的优化使用虚拟列表的方式 重组件的优化 以下代码原理是使用requestAnimationFrame(callback) 方法 vue2写法 Test01.vue <template><div class"container">&…