WebToApp 扩展模块(Extension Modules)配置实战:在「编辑通用配置」中为 APK 应用挂载 JS/CSS 扩展、用户脚本与 MV3 浏览器扩展
【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-app
导读
本篇技术指南聚焦 WebToApp 中Extension Modules(扩展模块)这一能力卡片:它位于每个应用的「编辑通用配置(Edit Common Config)」编辑器内,负责把项目内已安装的扩展(JS/CSS 模块、用户脚本、MV3 Chrome 扩展)按应用粒度挂载到生成的 APK 中。读完本文,你将掌握extensionEnabled、extensionModuleIds、extensionFabIcon三个配置项的完整语义,理解扩展从「模块库管理 → 应用配置 → WebView 生命周期注入」的完整链路,并能读懂仓库内置模块的module.json清单,动手配置出属于自己应用的扩展组合。
一、功能定位:给“已发布的应用”留的后门
WebToApp 的扩展体系在设计上有一个鲜明特点:应用打包发布之后依然可扩展。扩展不写死在应用逻辑里,而是由统一的ExtensionManager管理、由 WebView 在页面生命周期钩子处注入,因此同一套扩展体系同时服务于“打包前的配置”与“打包后的运行”两个阶段。本文要讲解的正是打包前配置这一环——在「编辑通用配置」的Extension modules卡片里完成三个关键设置。
在编辑器中的入口位置见 编辑通用配置总览:在应用卡片上点击⋮→Edit Common Config(Web 类应用直接显示为Edit),在「Extensions & network」分区即可找到Extension Modules卡片。
二、卡片上的三个配置项
该卡片提供三个核心选项,分别对应数据模型WebApp中的三个字段(见 WebApp.kt):
| 卡片选项 | 对应字段 | 类型与默认值 | 作用 |
|---|---|---|---|
| Enable | extensionEnabled | Boolean = false | 总开关:仅为当前应用启用/停用整套扩展注入 |
| Selected modules | extensionModuleIds | List<String> = emptyList() | 选择哪些已安装模块运行在该应用中(按模块id精确匹配) |
| FAB icon | extensionFabIcon | String? = null | 扩展面板悬浮按钮(Floating Button)的图标,空值使用默认图标 |
这三个字段在打包时会被写入 APK 的运行时配置。在 ApkConfigJsonFactory.kt 中可以看到它们被序列化为 JSON 配置项:
"extensionEnabled" to extension.enabled, "extensionFabIcon" to extension.fabIcon, "extensionModuleIds" to extension.moduleIds,对应的运行时配置块定义在 ApkConfig.kt:
data class ExtensionBlock( val enabled: Boolean = false, val moduleIds: List<String> = emptyList(), val embeddedModules: List<EmbeddedExtensionModule> = emptyList(), val fabIcon: String = "" )值得注意的是embeddedModules字段:与“按 id 引用已安装模块”不同,它是随 APK 内嵌的模块完整快照,用于保证引用模块的配置在目标设备上一定可用。
三、被选中模块的运行时行为
仅仅在卡片上勾选模块还不够,理解“勾选之后发生了什么”才能正确排错。运行时有两套匹配逻辑叠加:
- 应用级过滤:只有
extensionModuleIds里列出的模块(且处于 enabled 状态)才会进入候选集; - 页面级过滤:模块自身的
urlMatches规则决定它在哪些 URL 上真正执行。
ExtensionManager.generateInjectionCode()(见 ExtensionManager.kt)同时按runAt(注入时机)与sourceType(排除用户脚本/Chrome 扩展,它们走独立的注入通道)过滤后,才把模块代码包裹进带try/catch的 IIFE 注入页面:
val matchingModules = getModulesForUrl(url).filter { it.runAt == runAt && it.sourceType != ModuleSourceType.CHROME_EXTENSION && it.sourceType != ModuleSourceType.USERSCRIPT && it.sourceType != ModuleSourceType.GREASYFORK }也就是说:即便模块在卡片中被选中,它仍必须满足“URL 匹配”且“运行时机正确”才会生效——这解释了为什么某些模块在特定页面不工作。
运行时机(Run time)
模块数据模型ExtensionModule的runAt字段(ExtensionModule.kt)定义了五种注入时机:
ModuleRunTime | 触发的 WebView 生命周期事件 |
|---|---|
DOCUMENT_START | onPageStarted(文档尚未开始解析) |
DOCUMENT_END | onPageFinished/DOMContentLoaded |
DOCUMENT_IDLE | 页面加载完成之后(load事件) |
CONTEXT_MENU | 长按弹出上下文菜单时 |
BEFORE_UNLOAD | 页面卸载之前(beforeunload) |
从源码看,该字段在ExtensionModule数据类中的默认值为DOCUMENT_END(见 ExtensionModule.kt),这也是 JS Modules 文档 中标注的默认值。
URL 匹配规则(urlMatches)
每个模块还携带一组 URL 匹配规则(Chrome 风格 glob 或正则),见matchesUrl()实现(ExtensionModule.kt):
isRegex: false(默认)——Chrome 风格 glob:*匹配任意内容;*://展开为(https?|ftp|file)://;*或<all_urls>匹配一切 URL;其余正则元字符会被转义。glob 匹配失败时降级为大小写不敏感的contains子串匹配;isRegex: true——Java 正则,带200ms 超时(ExtensionModule.kt),超时视为“不匹配”,防止恶意或低效正则在每次页面加载时卡死 UI 线程;编译后的正则还经过一个容量 64 的有界 LRU 缓存;exclude: true——命中即从结果集中剔除(排除规则优先于包含规则)。
匹配语义:若同时存在 include 与 exclude 规则,先排除后包含;若只有 exclude 规则且未命中,则默认放行。
四、可以挂载哪几类扩展
「Selected modules」的可选范围来自Extension Modules 管理页(入口:主界面⋮→ Extension Modules,见 管理文档)。四类来源会被归一化进同一个内部模型ExtensionModule(见 扩展作者指南):
| 类型 | 形态 | 典型用途 |
|---|---|---|
| JS Module | module.json清单 +main.js(可选 CSS) | 带配置 UI 和悬浮面板的自定义功能,能力最强 |
| CSS Module | 纯样式覆盖(仍需一个main.js桩文件) | 主题、重排版、深色模式 |
| Userscript | Tampermonkey/Greasemonkey 风格.user.js | 移植现有用户脚本,暴露GM_*API |
| Chrome MV3 | Manifest V3 Chrome 扩展 | 移植浏览器扩展,暴露chrome.*API |
归一化的关键证据在数据类ExtensionModule(ExtensionModule.kt):它同时包含sourceType(CUSTOM / USERSCRIPT / CHROME_EXTENSION / GREASYFORK)、manifestJson、gmGrants、backgroundScript、popupPath、world(ISOLATED/MAIN)等字段,说明四类扩展共用同一套配置、分享与注入通道。
管理页提供的核心能力(对应 管理文档):
- List & toggle——按应用开关已安装扩展(对应
extensionModuleIds的选择操作); - Editor——创建/编辑模块:清单、JS、CSS、配置项与交互面板,语法见 JS Modules;
- Market——浏览社区模块市场,发布方式见 Publish to the Market;
- Browser extensions——在线搜索 Chrome Web Store 并安装 MV3 扩展,见 Chrome MV3;
- AI developer——跳转到 Agent 用 AI 生成扩展。
五、模块清单module.json深度解读
勾选模块前,读懂它的清单有助于判断该模块是否适合你的应用。以仓库内置模块为例,reading-mode 模块清单 完整展示了常见字段:
{ "id": "wta-reading-mode", "name": "Reading Mode", "icon": "menu_book", "category": "READING", "runAt": "DOCUMENT_END", "urlMatches": [ { "pattern": "*", "isRegex": false, "exclude": false } ], "permissions": ["DOM_ACCESS", "CSS_INJECT", "STORAGE"], "configItems": [ { "key": "theme", "name": "Theme", "type": "SELECT", "defaultValue": "light", "options": ["light", "sepia", "dark"] }, { "key": "fontSize", "name": "Font size (px)", "type": "NUMBER", "defaultValue": "18" } ] }关键字段在 ExtensionModule.kt 中有严格的数据类型约束:
category——23 个枚举值之一(CONTENT_FILTER、CONTENT_ENHANCE、STYLE_MODIFIER、THEME、FUNCTION_ENHANCE、AUTOMATION、NAVIGATION、DATA_EXTRACT、DATA_SAVE、INTERACTION、ACCESSIBILITY、MEDIA、VIDEO、IMAGE、AUDIO、SECURITY、ANTI_TRACKING、SOCIAL、SHOPPING、READING、TRANSLATE、DEVELOPER、OTHER),每个类别带 Material 图标与多语言名称/描述(ExtensionModule.kt);permissions——31 种权限枚举(如DOM_ACCESS、CSS_INJECT、STORAGE、COOKIE、LOCATION、EVAL等),其中dangerous = true的权限(如COOKIE、NETWORK、CAMERA、MICROPHONE、LOCATION、EVAL、IFRAME)在审核时会额外审查。注意:权限目前是展示性的,运行时并不据此做沙箱隔离;configItems——22 种配置控件类型(TEXT、TEXTAREA、NUMBER、BOOLEAN、SELECT、MULTI_SELECT、RADIO、CHECKBOX、COLOR、URL、EMAIL、PASSWORD、REGEX、CSS_SELECTOR、JAVASCRIPT、JSON、RANGE、DATE、TIME、DATETIME、FILE、IMAGE),最终以configValues: Map<String, String>的形式在运行时通过getConfig(key, defaultValue)暴露给脚本(ExtensionModule.kt)。
另一个简洁示例是 hello-world 模块清单:它演示了最小可行的模块结构——greeting(TEXT)与durationMs(NUMBER)两个配置项、DOCUMENT_END运行时机、*全量 URL 匹配。
main.js的编写契约(详见 JS Modules):代码被包裹在带try/catch的 IIFE 中(报错进console.error且不破坏页面),可直接访问__MODULE_INFO__、__MODULE_CONFIG__、__MODULE_UI_CONFIG__、__MODULE_PANEL_HTML__、getConfig(key, defaultValue)等全局;因为外层是 IIFE,顶层不能出现return语句。
六、FAB 图标与悬浮面板
extensionFabIcon控制扩展面板悬浮按钮的图标。当模块带有panelHtml或调用window.__WTA_MODULE_UI__.register({...})注册时(ExtensionModule.kt 的自动注册逻辑),WebView 内会显示一个悬浮按钮,点击展开该模块的交互面板。uiConfig(ExtensionModule.kt)支持:
| 配置项 | 默认值 | 说明 |
|---|---|---|
type | FLOATING_BUTTON | UI 形态,目前仅悬浮按钮 |
autoHide | false | 是否自动隐藏 |
autoHideDelay | 3000(毫秒) | 自动隐藏延迟 |
initiallyHidden | false | 初始是否隐藏 |
showOnlyOnMatch | true | 是否仅在 URL 匹配时显示 |
面板 HTML 内通过data-wta-action属性绑定window.__wta_module_action_<name>处理器,并可使用var(--wta-*)主题变量匹配应用主题(见 JS Modules)。
七、扩展的分享、导入与持久化
配置卡片面向“已安装模块”的选择,而模块本身的流转依赖以下机制(见 ExtensionManager.kt):
- 存储布局:用户模块保存在应用私有目录
extension_modules/modules.json;代码与 CSS 以侧车文件形式分离存储(code_<id>.js、css_<id>.css、codefiles_<id>/),modules.json只保留元数据;内置模块的启用状态单独存于builtin_states.json。若modules.json解析失败,管理器会自动把损坏文件备份为modules_backup_<时间戳>.json后再恢复(ExtensionManager.kt); - 文件交换:单个模块导出为
.wtamod,多模块打包为.wtapkg;导入时会重新生成id并强制builtIn = false; - 二维码分享码:
WTA1:(完整 gzip + Base64)与WTA2:(仅携带与默认值的差异字段 + 最高压缩,兼容旧版解码器),单二维码物理上限为 2953 字节(ExtensionModule.kt),超过上限自动切换为 V2 编码。
八、使用中的三个重要注意点
扩展作者指南 明确指出了与浏览器扩展平台的三个行为差异,配置与排错时务必记住:
- 用户脚本的
GM_*函数不受@grant门控——全部无条件暴露; - Chrome 的
ISOLATED/MAIN世界并非真正隔离——Android WebView 只有一个 JS 上下文,隔离是模拟的; GM_notification仅记录日志;MV3 的“后台 service worker”实际是一个隐藏 WebView,而非真正的 service worker。
九、完整的配置工作流
结合以上全部机制,推荐的最小可用流程如下:
- 在Extension Modules 管理页(主界面
⋮→ Extension Modules)安装或创建所需模块(内置模块无需安装); - 打开目标应用的Edit Common Config→Extension modules卡片;
- 打开Enable开关(写入
extensionEnabled = true); - 在Selected modules中勾选需要的模块(写入
extensionModuleIds); - 按需修改FAB icon(写入
extensionFabIcon); - 保存后重新导出/打包 APK,运行时
WebViewManager会按各模块的runAt与urlMatches在对应页面注入代码。
若某个模块在页面中不生效,优先检查:该模块是否在卡片中被勾选、是否enabled、其urlMatches是否覆盖当前页面、runAt是否与预期时机一致,以及是否属于需要走独立注入通道的用户脚本/Chrome 扩展类型。
十、延伸阅读
- 编辑器全量能力卡列表:编辑通用配置总览
- 扩展模块管理页:Extension Modules(管理)
- 四类扩展总览与注入模型:Extension Authoring
- JS 模块清单与
main.js契约:JS Modules - 用户脚本
GM_*API 参考:Userscripts - 源码级实现:ExtensionModule.kt(数据模型/URL 匹配/分享码)、ExtensionManager.kt(管理/注入)、ApkConfig.kt(打包配置块)
- 可参考的完整示例清单:modules 目录、模块注册表
【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考