作者:Kirill Rakhman
译者:承香墨影
本文由 Kirill Rakhman 授权翻译并发布。
这篇文章是《你的第一个 Kotlin 版的 Firefox 插件》的续集。你应该先阅读它,以了解在 Kotlin 中编写的 Firefox 扩展插件的基本要求和设置。
在这篇文章中,我们将使用 Kotlin 重写 Mozilla 教程中的 第二个扩展插件。这个扩展插件包含一个带有弹出式菜单的工具栏按钮,可让你用自定义的图像替换当前选项卡的内容。
关于这个插件的一个有趣的地方是,它不像以前那样需要与 WebExtensions API 进行交互。我们将探索两种可能的方案来做到这一点,一种是全但有点繁琐的方式,一种是减少编码量的动态方式。
你可以通过这个地址,找到 Firefox 的官方教程:
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Your_second_WebExtension
你还可以通过这个 Github 地址,查看到完整的项目:
https://github.com/cypressious/second-firefox-extension-kotlin
扩展
我们扩展插件的主要是一个工具栏按钮。当点击按钮时,会弹出一个窗口,你可以在其中选择要显示的图片。
由于扩展脚本与内容处理直接是隔离运行的,因此在工具栏弹出窗口中运行的脚本无法直接操作选项卡的 DOM 。这就是为什么我们需要在当前 DOM 中,再注入第二个处理脚本,让这两个脚本通过消息进行通信。
设置
根据所有 Firefox 扩展插件的要求,我们需要声明一个manifest.json
告诉浏览器我们的扩展功能。
第一个难点
如前所述,我们的扩展将由两个独立的脚本文件组成。但是,这带来了一个小问题,因为 Kotlin JS 编译器会将所有 Kotlin 代码合并到一个 JS 文件中。根据KT-6168的 issue,可以通过配置将允许编译成多个文件。
KT-6168:
https://youtrack.jetbrains.com/issue/KT-6168
为了克服这个限制,我们将创建两个模块,一个包含弹出脚本,另一个包含内容脚本。以下是设置所需的文件build.gradle
和setting.gradle
文件:
build.gradle:
setting.gradle:
Kotlin 的代码将被放置在popup/src/main/kotlin
和content_script/src/main/kotlin
中。
弹出框
在manifest.json
中,我们声明,扩展插件有一个工具栏按钮,点击它将显示一个弹出窗口的布局位于popup/choose_beast.html
。
即使我们在 Kotlin 文件中只是编写逻辑代码,HTML 也必须引用编译的 JS 输出以及必需的 kotlin.js stdlib。
Kotlin
我们代码的入口 main()
函数是打开弹出窗口时运行的函数。它将立即在当前标签中注入我们需要的内容脚本,然后监听弹出窗口中的点击。
注入一个脚本是异步的,并且返回一个Promise
,这个操作在 Kolin 不需要做额外的处理,它是原生支持的。
外部声明-静态方法
为了方便和浏览器交互,我们将使用 browser
,它包含所有和浏览器相关的顶级属性操作的 APi。但是 Kotlin 编译器不能提前知道这些声明,因此我们需要提前写下它们,进行标记,才能正常编译。在这个例子中,Kotlin 提供了 external
修饰符,来对它进行修饰。
编写这些声明的过程基本上就是阅读executeScript 的 API文档 并手动转义成 Kotlin 代码。转义是简洁明了的,我们不必声明我们不使用的属性。例如,参数executeScript
有 6 个不同的属性,但是因为我们只需要其中一个名字为 file
的属性,所以我们省略了其他 5 个。
executeScript 的 Api 文档地址:
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/executeScript
编译器不能验证这些声明是否真正正确,所以我们用转义它的代码来让编译正确。在一个更好的环境下,我们应该可以简单地将这些声明,作为一个 Gradle 依赖项来进行添加,我正在做这些,这就属于另外的一篇文章的内容了。
其他的 kotlin 代码
接下来,我们实施监听点击,并发送消息给我们之前注入的内容脚本。
我们为整个文档设置一个点击监听器。当一个元素被点击,我们查询活动选项卡(发送消息需要一个选项卡的 ID )。然后handleClick
,我们调用它来显示或隐藏当前选项卡的内容,并发送消息来显示或隐藏我们自定义的图像。
请注意,browser.tabs.sendMessage
要求 message 参数是一个单纯的 Javascript 对象。Kotlin JS 不支持 Object literals(KT-7935),这就是为什么我们创建一个帮助转换的函数 jsObject()
。我们使用特殊的内置函数js
将常量字符串转换为 Javascript 对象。然后我们在对象上通过 lambda 表达式来初始化它并返回它。该对象具有特殊的类型,dynamic
声明意味着编译器将不会执行任何类型检查,并允许我们对其进行任意的调用。我们可以使用它来分配任意的属性。
代码依赖于external
你可以在Github源码中,找到的更多的声明。
https://github.com/cypressious/second-firefox-extension-kotlin/blob/master/popup/src/main/kotlin/helpers.kt
实现注入的内容脚本
内容脚本直接在选项卡中运行,并侦听弹出消息。这是整个代码:
为了防止脚本运行多次,我们在 window
上设置一个属性 hasRun
为 true
作为标记。我们使用内置的函数asDynamic
来与window
通信,dynamic
可以方便我们读取和写入任意的属性。
接下来,我们听取消息,并收到他们要么显示或删除一个自定义的图像。
使用外部声明 - 动态的方式
同样,我们需要调用在外部属性中定义的 API browser
对象。作为编写整个声明的替代方法,我们可以采取一个捷径,简单地声明。
./gradlew runDceKotlinJs — continuous
正如我们已经看到的,这可以让我们对这个对象进行任意的调用,同时我们可以保证编译时的安全。
测试插件
要启动安装了扩展程序的 Firefox 实例,我们运行
./gradlew runDceKotlinJs — continuous
和
web-ext run
代码的任何改变,都会立即被编译渲染。
调试内容脚本
如果需要的话,我们也可以看看当前浏览器中的代码。要调试内容脚本,请打开开发人员工具,然后单击 调试器。你应该能够看到内容脚本的(Kotlin)代码,甚至设置断点。
调试 Popup 脚本
要调试弹出脚本,请打开about:debugging
并单击扩展下面的“调试”。
一个新的开发者工具窗口应该打开,请确保你右上角切换弹出按钮是可见的。
不幸的是,在这里我们只能看到生成的JS代码。这是 Firefox bugtracker 的一个众所周知的问题,希望这个问题很快就会解决。
要调试其他类型的脚本,请查看 https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Debugging。
小结
这篇文章中,我们看到了如何在 Kotlin 中编写复杂的 Firefox 扩展插件。在过程中,我们需要解决几个问题:
要生成多个 JS 输出文件,我们需要制作多个模块。
为了调用外部定义的 API,我们需要手工编写定义或放弃类型安全。
要创建JS对象,我们需要一个神奇的函数来写一些普通的 JS 对象
除了这些,编码的过程是相当愉快的。Kotlin 的语法和语言特性使代码简洁而有趣。
这位外国小哥,迷恋上用 Kotlin 写插件了,并且越陷越深,如果有持续的文章,我会据需跟踪翻译。
利用 Kotlin 做一些有趣的事情,例如一个浏览器插件。今天的文章你喜欢吗?留言告诉我。
本文由 Kirill Rakhman 授权翻译并发布。
原文链接:
https://medium.com/@Cypressious/your-second-firefox-extension-in-kotlin-bafd91d87c41
今天在承香墨影公众号的后台,回复『成长』。我会送你一些我整理的学习资料,包含:Android反编译、算法、设计模式、虚拟机、Linux、Kotlin、Python、爬虫、Web项目源码。
推荐阅读:
听说喜欢留言的人,运气都不会太差
点击『阅读原文』查看更多精彩内容