Air 系列应用交流/轻邮件(from Thunderbird)|AirMail · 鸿蒙 PC 适配全记录

轻邮件(from Thunderbird)|AirMail · 鸿蒙 PC 适配全记录

【Air 系列 · 鸿蒙 PC 适配全记录】21 / 56

■ 应用:轻邮件(from Thunderbird)(AirMail)
■ 定位:邮件/日历/联系人套件(Thunderbird)
■ 上游:Thunderbird | 路线:Gecko 平台层 | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/

————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/

PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:(AtomGit 仓库建立后回填)

环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743

▍一、为什么要适配 AirMail

桌面邮件客户端是 PC 上寿命最长的软件品类之一。Mozilla Thunderbird 以开源身份维护着完整的邮件、日历、地址簿与任务体系,其 Supernova 界面的三栏布局——文件夹树、消息列表、阅读窗格——是无数用户每天的工作台。把 Thunderbird 带到鸿蒙 PC,意味着把 Gecko 渲染引擎、mailnews 邮件协议栈、 NSS/NSPR 安全层整条链路搬到 musl 沙箱里,难度在应用移植谱系中属于最高一档。

AirMail(应用名"轻邮件")的移植路线经历了一次关键升级。工程最初沿用 AirZotero 验证过的"零源码移植"范式:复用 AirWeb 构建的 Gecko 142.0a1 OpenHarmony 平台层,把 Thunderbird 142.0 官方 zh-CN 发布包的 omni.ja 做手术改造为应用层。壳层与内核链路验证全部通过后,主窗 JavaScript 层却整体失效——Ci.nsMsgFolderFlags 等 mailnews 接口全部 undefined。结论很快定位:Zotero 的 chrome 是纯 JavaScript,零源码替换可行;而 Thunderbird 的大量 mailnews、calendar、chat C++ XPCOM 组件编译在它自己的 libxul 里,Firefox 内核根本不包含这些符号。工程随即转入源码级路线,以 Thunderbird 142.0 官方源码(mozilla 142.0 加 comm 邮件层)为基底,重放 AirWeb 的 OHOS 平台层补丁集,构建出带完整 mailnews 的 TB OHOS 内核。

当前工程包名 com.airmail.app,版本 1.0.0,deviceTypes 声明 phone、tablet、2in1。内核产物为 23 个 Gecko OHOS .so,其中 libxul.so 达 151MB(含 .permission JIT 段),随 HAP 打包后整包约 267MB。module.json5 声明 ALLOW_WRITABLE_CODE_MEMORY(SpiderMonkey JIT 需要 W^X 解锁,且要求 Profile ACL 与清单声明同时满足)、INTERNET 与 READ_PASTEBOARD,并配置 compressNativeLibs:false 以保证对齐敏感的大 ELF 可以被 mmap 直读。部署验证设备为 HUAWEI MatePad 11.5。

▍二、先划清边界:不把编译通过当成适配完成

AirMail 的边界问题有独特的呈现方式:内核构建成功、主窗渲染正常,仍然不等于一个可用的邮件客户端。撰写邮件、账户配置这类核心流程依赖的顶层窗口、弹窗与增量渲染能力,在单 EGL Surface 的架构约束下各有各的缺口。工程阶段日志把每一项的真实状态逐条列表,本文同样按此口径呈现。

层次 传统桌面方案(Thunderbird on Linux) 鸿蒙侧当前方案
应用底座 TB 官方 Linux x86_64 发行包 源码级构建 TB 142.0 OHOS aarch64 内核(libxul 含 mailnews/calendar/chat)
应用层 官方 omni.ja 整包 stage_airmail.sh 幂等手术的 omni.ja(OHOS 适配层注入)
窗口系统 多顶层 X 窗口自由开合 单 EGL Surface;第二顶层窗口改道 tab 化装载
用户输入 X11 键鼠 + IME 框架 ArkTS 桥注入 + nsWindow 编辑器直插修复
剪贴板 nsIClipboard 系统服务 air_*.txt 双向文件通道代理
账户配置 向导弹窗交互 user.js 种子直注 + 向导 tab(向导弹窗族未通)
品牌标识 Thunderbird / Mozilla 手术去品牌为轻邮件(brand.dtd、brand.ftl、properties)

这张表的核心信息是窗口系统一行:桌面 Thunderbird 的功能边界天然假设"可以随时开一个新顶层窗口",而鸿蒙侧的 Gecko 平台层只有一块合成表面,这是后续所有难点(撰写窗、向导弹窗、弹窗校验)的共同根源。工程没有回避,而是把每一个死窗逐个改道。

▍三、鸿蒙版的整体架构

架构复刻 AirZotero stage2 的零编译壳层模式:ArkTS 壳不含任何 C++ 源码(cpp 目录只有占位 target 与 POST_BUILD 拷贝),Gecko 内核 23 个 .so 走 hvigor 模块级通道随包分发,运行时 dlopen;应用层是手术后的 omni.ja,经 taskpool 流式拷贝部署到沙箱 GRE 目录。

text
EntryAbility(GRE 部署 / omni 换版清 startupCache / URI 与分享接收)
└── NativeShellPage(native 轨主页面 + TB 语义菜单 + IME 桥)
│ libgeckoview.so(NAPI 桥)/ air_*.txt 双向文件通道
▼
Gecko OHOS 平台层(23 个 .so,运行时 dlopen)
├── libxul.so(151MB:Gecko 142 + TB mailnews/calendar/chat XPCOM + .permission JIT 段)
├── libgeckoxcomp.so(XComponent 合成)+ NSS/NSPR 全家 + librnp(OpenPGP)
└── omni.ja(TB 142 手术产物):messenger.xhtml 主窗 + 注入器 airmail_ohos_boot.js
├── A2~A7:触摸 shim / 剪贴板代理 / confirm 通道 / ready 信号
├── A8:命令桥(airmail.command pref 三元组 → 动词分发器)
└── A9:airmailCompose tab 类型(撰写窗 tab 化装载)


仓库主要目录如下:

text
AirMail/
├── AppScope/ # 应用级配置(com.airmail.app / 1.0.0)
├── PORTING.md # 移植档案:身份、手术清单、风险登记册
├── ai_log/ # v0 立项到 v4 撰写窗攻坚的阶段总账
├── upstream/ # thunderbird-142.0-zh-CN.tar.xz 官方包(只读)
├── staging/ # omni 手术工作区(每次重建)
├── scripts/
│ ├── build_thunderbird.sh # TB 142 OHOS 内核源码级构建
│ ├── stage_airmail.sh # omni.ja 幂等手术脚本(含自检)
│ └── inject/airmail_ohos_boot.js # 注入器:触摸/剪贴板/命令桥/撰写 tab 化
├── diag/ # 部署截图与 hilog 取证
├── native/patches/ # libxul 层补丁留档(如 nsWindow IME 直插)
└── entry/src/main/
├── libs/arm64-v8a/ # 23 个 Gecko OHOS .so
├── cpp/CMakeLists.txt # 占位 target + POST_BUILD 拷贝(零 C++ 源码)
├── resources/rawfile/
│ ├── omni.ja # TB 142 手术产物
│ └── omni_version.txt # 版本戳(增量部署依据)
└── ets/
├── entryability/EntryAbility.ets
├── pages/NativeShellPage.ets # TB 语义菜单 + 命令桥发送 + CMD-ACK 取证
├── native/FirefoxBridge.ets 等 # Gecko 桥与菜单桥
└── models/ShellCommand.ets # 动词白名单(msgNew/space/settings 等)


▍四、核心适配过程

### 1. 零源码路线的证伪与源码级决策

立项首日完成的手术底稿并非无用功:壳层全链路(GL/EGL、事件循环、browser.xhtml 跳转 shim、AppConstants 手术)在同壳跑 Zotero omni 时渲染完整,证明壳与内核链路健康;换成 TB omni 后主窗 JS 层全灭、NS_ERROR_XPC_BAD_IID 爆发,A/B 对照把缺陷精确定位到内核组件面。这个证伪过程直接决定了源码级路线的技术形态——不必新写平台层,只需以 TB 142.0 官方源码为基底重放 AirWeb 的 OHOS 补丁,让 mailnews 的 C++ 组件随 libxul 一起编出来。构建布局沿用 TB 官方 tarball 同构:mozilla 全树加 comm 真实子目录,mach 在树根运行。

### 2. 大规模树同步与补丁重放

AirWeb 基底是 142.0a1 nightly,与 TB 142.0 release 之间存在数千文件的 API 差异。工程用 rsync -rcn 生成 8224 条校验和差异清单:内容无 OHOS 痕迹的 7336 个文件直接覆盖到 142.0,有痕迹的 746 个保留后逐文件重放结构性补丁——PContent.ipdl 的字体段宏、webrender 线程 QoS 打标、AppWindow 固有尺寸防护、nsWindowWatcher 的空指针防护、ffvpx 的 aarch64 配置等十余处。两个教训值得记录:rsync 保留 tarball 旧 mtime 导致 make 认为无变化不重编,同步后必须 touch;补丁保留过滤器不能只看 ohos 关键词,ffvpx 的补丁以 __aarch64__ 判定,被误覆盖后 x86 头文件混进 aarch64 编译。Rust 侧另需对齐 795 个 Cargo.toml 与 vendor 目录,并切除 audio_thread_priority 的 dbus 默认特性。

### 3. 内核产出与 ports/ohos 接线

TB 应用树默认不产出 libgeckoview.so——AirWeb 是以 --enable-application=mobile/ohos 构建才有这一层。工程在 app.mozbuild 追加条件目录:toolkit 为 openharmony 时编入 ports/ohos,产出 115KB 的 libgeckoview.so(NAPI 桥)与 libgeckoxcomp.so 副本;同时刻意不引入 mobile/ohos 层,避免其 geckoview-prefs 把 defaultChromeURI 指向浏览器页而与 messenger 冲突。最终 libxul.so 151MB,nm 验证含 589 个 nsMsg 系符号;构建产物经 llvm-objcopy 注入 .permission JIT 段后 staged 进工程。真机首启即见到 Thunderbird Supernova 三栏完整渲染,NS_ERROR_XPC_BAD_IID 计数为零——mailnews 组件面缺失的症结从根上消除。

### 4. omni 手术与命令桥三层根因

应用层的手术由 stage_airmail.sh 幂等完成:AppConstants 五常量短路、greprefs 关停、性能 prefs 文件新建(对齐 AirWeb 全量参数)、触摸手术族注入、剪贴板与 confirm 文件通道、ready 信号挂钩、去品牌替换,最后 STORED 方式重打包。菜单命令桥(ArkTS 写 pref 三元组,注入器 A8 分发器轮询执行)曾经历三层根因:其一,注入器入口以 AppConstants 判 OHOS,而官方 omni 里该常量烤死为 gtk,整个注入器自首日起静默零执行,改读手术注入的 airmail.ohos.omni 标记 pref;其二,profile 的 startupCache 按构建号复用 chrome JS 字节码,旧注入器的编译产物跨 omni 版本继续服役,omni 换版时必须清除 startupCache;其三,Gecko 142 的 API 代差——DOM 对象不再暴露可调用的 QueryInterface、撰写命令需经命令系统路由、空间工具栏是数组而非字典。三层修完后,菜单 TB 语义化(文件、前往、工具)、设置页、日历与邮箱空间切换全部打通,全程零崩溃零 XPC 错误。

### 5. 撰写窗 tab 化与 IME 直插

撰写邮件在桌面是独立顶层窗口,命中单表面架构的第二窗口死区。工程复用"替代哲学"改道 tab:注入器注册 airmailCompose tab 类型,以 chrome iframe 装载 msgcompose.xhtml 并轮询等待 tabmail 升级,且刻意不实现会话持久化避免重启恢复出无参数空撰写窗;命令侧自建 nsIMsgComposeParams 暂存在主窗对象上,手术在 ComposeStartup 函数头注入参数 fallback。装载与初绘打通后,输入层又暴露 Gecko 内部断点:IME 插入文本按 eOnlyCurrentWindow 只在派发窗口自己的文档树找焦点元素,tab 内子文档的输入框永远找不到。修复落在平台层 nsWindow.cpp 的 OnInsertText——优先经 nsFocusManager 取全局焦点元素直插编辑器,失败再回退原通道,补丁留档 native/patches/。DOM 层验证收件人输入逐字符递增,但屏幕显示层仍不刷新,成为当前最深的遗留问题。

▍五、适配中遇到的几个难点

### 难点一:mailnews 组件面与零源码范式的适用边界

零源码移植在 Zotero 上成立的前提是"应用层纯 JS",这个前提对 Thunderbird 不成立。难点不在发现结论,而在证明过程的设计:同壳同内核跑两个 omni 的 A/B 实验,把变量收敛到唯一——应用层组件面。由此得到一条对后续移植者有直接价值的判据:评估"复用内核加替换应用层"路线前,先确认目标应用的 XPCOM 组件清单是否超出内核构建的组件面,超出部分决定了是否必须源码级构建。

### 难点二:跨版本树同步的错误放大

保留 OHOS 修改的文件若上游 API 已变,会成为逐个撞墙的错误放大器:CSP 接口签名变更、ipdl 定义漂移等问题散布在十余个文件里。工程的应对是把"保留"细分为纯日志埋点(直接覆盖)与结构性适配(基底更新加重放 hunk)两类,逐文件处理并即时编译验证。同步过滤器的盲区教训同样重要——判定依据是 ohos 关键词时,__aarch64__ 这类宏守卫的补丁会被误判为上游文件而覆盖,随后以 x86 汇编头混入编译的形式在远处爆炸。

### 难点三:字节码缓存让修复静默失效

pref 标记修复上机后命令桥依然全死,而向导能自动打开证明注入器上游段在执行。最终定位到 profile/startupCache:chrome JS 按 buildid 加 URI 复用 XDR 字节码,libxul 未重编时旧 messenger.js 的编译产物跨 omni 重打包继续服役,昨天的旧逻辑今天还在跑。这类问题没有报错、没有日志,只有"改了不生效"的表象。工程把 purgeStartupCache() 挂进 GRE 部署的 omni 换版分支,并在排查方法论里补充:凡修改 omni 注入层,先清字节码缓存再下结论。

### 难点四:第二顶层窗口家族

单 EGL Surface 架构下,一切独立顶层窗口都是死窗。撰写窗之外,账户向导的完成按钮(OAuth2 授权窗)、高级配置窗、验证方式下拉面板全部属于此家族。撰写窗以 tab 化根治,向导则用 user.js 直种账户绕过;弹窗替代通道(confirm/alert 走文件双向确认)已有基建,但 TB 校验弹窗的接点尚未全部接通,导致发送链的空收件人校验都无提示。这是当前功能边界里最重的一块缺口,工程的处置是把家族成员逐个登记、逐个选型(scene-merge、tab 化、文件通道三范式),而不是笼统标记"弹窗不支持"。

### 难点五:应用内 DNS 与网络栈验证前置

账户配置后的收发信实测被应用内 DNS 故障前置阻塞:musl resolver 在 Gecko 进程内重试失败、libresolv 加载失败,而系统层 ping 正常——问题精确定位在应用内解析链。工程文档给出的下一步是本地 hosts 或 IP 直连绕过 DNS,先验证 socket 与 TLS 层(NSS 通路在同类工程已有实证)。这个难点的处理方式体现了分阶段验证纪律:收发信是端到端流程,DNS、socket、TLS、IMAP 协议、邮件解析是五个可独立验证的层,先打通底层再谈上层,每一层的证据独立留档。

▍六、编译、安装与启动

工程的构建分两层。内核层由 scripts/build_thunderbird.sh 驱动:comm 以 mozilla 树内真实子目录布局就位后,mach configure 加 build 完成 TB 142 OHOS aarch64 全量构建,产物经 llvm-objcopy 注入 .permission 段后拷入 entry/src/main/libs/arm64-v8a/;libxul 增量构建约五分钟一轮,无需重跑手术(omni 未变时)。应用层由 scripts/stage_airmail.sh 从官方包重建手术 omni.ja,脚本幂等并带 13 项自检(含 STORED 打包、manifest 双候选、品牌替换断言)。

ArkTS 工程的标准构建:

bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk

hvigorw --mode module -p module=entry assembleHap --no-daemon


产物位于 entry/build/default/outputs/default/entry-default-signed.hap(整包约 267MB,含 151MB 的 libxul.so)。调试签名的 Profile ACL 须包含 ALLOW_WRITABLE_CODE_MEMORY,与 module.json5 声明共同构成 JIT 授权。安装与启动:

bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airmail.app


首次启动会把 omni.ja 流式拷贝到沙箱 GRE 目录(版本戳比对做增量),omni 换版时自动清除 startupCache。部署前按纪律先 aa force-stop 换进程,再用 devecocli run --device <序列号> --skip-build 直接部署也是工程验证记录中的既定方式。

▍七、当前功能边界

以工程 ai_log 的真机功能现状表为依据,已覆盖的能力:

- 源码级 TB 142 OHOS 内核:libxul 含完整 mailnews/calendar/chat XPCOM,主窗 Supernova 三栏完整渲染(截图与视觉双通道确认);
- 菜单栏 TB 语义化:新建消息、收取、账户管理、空间切换、设置与附加组件入口;
- 设置页 tab、日历与邮箱空间切换(空间条高亮与迷你月历实证);
- 账户向导 tab 自动打开,表单填写、探测与手动配置前半流程可用;
- IME 软键盘弹收、滚动、单击、汉堡菜单回归正常(触摸手术族无铁律事故);
- 撰写窗 tab 化装载与 DOM 数据层输入(逐字符回读验证);
- 中文界面(zh-CN 官方包自带完整 locale)、去品牌后的轻邮件标识。

尚未覆盖或未收敛的能力:撰写窗的显示层(chrome XUL 子文档增量渲染不达屏,DOM 正常而画面不动,怀疑与 WebRender 增量失效链有关);tab 条触摸三轮复现零响应,根因未定;发送链受阻于弹窗死区(空收件人校验都无提示);收发信实测被应用内 DNS 故障前置阻塞,IMAP/SMTP 的 socket 与 TLS 层验证未开始;账户向导的弹窗三件(高级配置、OAuth2 完成、验证方式下拉)仍属死窗;S1 到 S14 的整体验收对照尚未完成。工程文档对这些缺口的记录与已覆盖项同表并列,没有模糊地带。

因此,当前版本更准确的定位是"源码级内核跑通、主窗与导航可用的早期阶段":它证明了 Thunderbird 这一级别的应用可以在鸿蒙侧完成源码级构建并渲染出完整主界面,但距可日常收发邮件的客户端还有一段明确列出的路。

▍八、总结

AirMail 的主要工程贡献是打通了 Thunderbird 的鸿蒙源码级构建:142.0 官方源码、AirWeb OHOS 平台层补丁、comm 邮件层三者的组合方案,连同跨版本树同步、补丁重放、workspace 对齐、dbus 切除的完整工序,都沉淀在脚本与阶段日志里。零源码路线的证伪同样有方法论价值——它划清了"复用内核"范式的适用边界,并给出了可操作的预判手段。

后续工作的优先级在阶段日志里已经排好:先攻 chrome XUL 子文档的增量渲染(从 WebRender 事务层取证),它同时卡着撰写窗显示层与 tab 条触摸两处;再以弹窗替代通道接通发送链;网络栈按 DNS、socket、TLS、协议四层递进验证后做真实收发信回归;最后完成 S1 到 S14 验收与文档收尾。每一步的验收口径与此前一致:真机操作、文件通道回执、截图取证三线齐全,证据不足不下结论。
楼主 8 天前
想参与讨论?登录 或 30 秒注册。