:App Extension + XPC 承載 Gecko 多進(jìn)程的完整拆解)
【免費(fèi)下載鏈接】reynard-browserAn experimental Gecko-based web browser for iOS 13.項(xiàng)目地址https://gitcode.com/gh_mirrors/re/reynard-browser點(diǎn)擊查看免費(fèi)下載Reynard Browser 是一款運(yùn)行在 iOS 13 上的實(shí)驗(yàn)性 Gecko 引擎瀏覽器。與 iPhone 上所有被強(qiáng)制使用 WebKit 的瀏覽器不同它把 Firefox 同款 Gecko 引擎移植到了 iOS并且不依賴 Apple 官方的 BrowserEngineKit 框架。它最核心的工程難題是如何讓 Gecko 的多進(jìn)程模型瀏覽器進(jìn)程 內(nèi)容進(jìn)程在 iOS 的 App Extension 隔離機(jī)制中跑起來(lái)。本文帶你完整拆解這個(gè)「App Extension XPC」的多進(jìn)程承載方案。倉(cāng)庫(kù)結(jié)構(gòu)速覽三個(gè)角色各司其職理解架構(gòu)前先看看 reynard-browser/ 倉(cāng)庫(kù)里與多進(jìn)程直接相關(guān)的部分目錄角色browser/Reynard/主應(yīng)用瀏覽器進(jìn)程 UI 層Swift UIKitbrowser/Helper/「Reynard Helper.appex」擴(kuò)展每個(gè)實(shí)例就是一個(gè) Gecko 子進(jìn)程容器browser/GeckoView/自研 GeckoView 移植層Runtime/Session/Prompts 等patches/對(duì) Gecko 引擎的修改ipc/、toolkit/xre/等目錄tools/development/下載 Gecko、打補(bǔ)丁、出 IPA 的腳本關(guān)鍵認(rèn)知Gecko 的每個(gè)子進(jìn)程tab 內(nèi)容進(jìn)程、GPU/Socket 進(jìn)程都不直接 fork 出來(lái)而是讓 iOS 以「App Extension 請(qǐng)求」的方式拉起 Helper.appex再用 XPC 把父子進(jìn)程接成一條 IPC 通道。第一步把 App Extension 改造成「進(jìn)程容器」擴(kuò)展聲明Application 類型 多實(shí)例Helper 擴(kuò)展的 Info.plist 是整個(gè)方案的地基NSExtensionPointIdentifier指向com.apple.ar.viewer讓系統(tǒng)按擴(kuò)展流程加載它真正起作用的是XPCService段ServiceType Application、_ProcessType App并開啟_MultipleInstances true——允許多個(gè)擴(kuò)展進(jìn)程并存正好對(duì)應(yīng) Gecko 需要的多個(gè)內(nèi)容進(jìn)程N(yùn)SExtensionPrincipalClass指向ReynardHelperMain即 Helper.swift 中注冊(cè)的主類。入口鉤子繞過(guò) XPC 類校驗(yàn)擴(kuò)展入口 main.m 做了一件「必要的小 trick」在調(diào)用真正的NSExtensionMain之前先把NSXPCDecoder的私有方法_validateAllowedClass:forKey:allowingInvocations:替換為空實(shí)現(xiàn)。因?yàn)楦缸舆M(jìn)程之間要傳輸?shù)念愋筒⒉辉谙到y(tǒng)默認(rèn)白名單里不做這一步 XPC 消息會(huì)直接解碼失敗。第二步XPC 雙向握手把子進(jìn)程接進(jìn) Gecko IPC父進(jìn)程側(cè)匿名 Listener 啟動(dòng)擴(kuò)展請(qǐng)求父進(jìn)程邏輯在引擎補(bǔ)丁 NSExtensionUtils.mm 中實(shí)現(xiàn)流程是創(chuàng)建一個(gè)NSXPCListener匿名監(jiān)聽器并 resume把listener.endpoint塞進(jìn)NSExtensionItem的 userInfo鍵名是ReynardXPCListenerEndpoint調(diào)用beginExtensionRequestWithInputItems:請(qǐng)求啟動(dòng) Helper.appex——系統(tǒng)隨即拉起了一個(gè)擴(kuò)展進(jìn)程擴(kuò)展連上來(lái)后父進(jìn)程通過(guò)私有方法_xpcConnection取出底層的xpc_connection_t交給 Gecko IPC 使用8 秒內(nèi)沒(méi)等到子進(jìn)程回 ping 就報(bào)超時(shí)錯(cuò)誤防止卡死。子進(jìn)程側(cè)從輸入項(xiàng)還原連接子進(jìn)程側(cè)在 Helper.swift 中完成鏡像動(dòng)作beginRequest(with:)收到NSExtensionContext從inputItems里取出ReynardXPCListenerEndpoint建立NSXPCConnection并 resume通過(guò) ExtensionBridge.mm 的XPCConnectionFromNSXPC()同樣是私有_xpcConnection拿到底層 libxpc 連接調(diào)用GeckoRuntime.childMain(xpcConnection:process:)把控制權(quán)交給引擎。Swift 側(cè)的橋接入口在 GeckoRuntime.swiftmain()走M(jìn)ainProcessInit父進(jìn)程childMain()走ChildProcessInit子進(jìn)程。引擎?zhèn)菴hildProcessInit 在擴(kuò)展里「復(fù)活」補(bǔ)丁 IOSBootstrap.mm 里的ChildProcessInit拿到 XPC 連接后調(diào)用xpc_connection_activate()讓 Gecko 的子進(jìn)程框架直接在這條 XPC 通道上跑 IPC 協(xié)議——從此擴(kuò)展進(jìn)程對(duì) Gecko 來(lái)說(shuō)就是一個(gè)「正常的」內(nèi)容進(jìn)程。GeckoChildProcessHost的 iOS 啟動(dòng)器也相應(yīng)從官方的ExtensionKitProcess換成了這套NSExtensionProcess見 GeckoChildProcessHost.cpp 補(bǔ)丁。第三步JIT 與內(nèi)存讓子進(jìn)程跑得動(dòng)Gecko 的內(nèi)容進(jìn)程需要 JIT 執(zhí)行 JavaScript而 iOS 的 App Extension 默認(rèn)是不給 JIT 權(quán)限的Reynard 用兩條路徑解決配對(duì) / TrollStore 路徑JIT/JITController.swift 監(jiān)聽GeckoRuntime.ChildProcessDidStart通知父進(jìn)程在 GeckoRuntime.swift 的childProcessDidStart中發(fā)出只針對(duì)tab類型進(jìn)程通過(guò) DDI 配對(duì)或 ptrace 方式為子進(jìn)程啟用 JIT。子進(jìn)程啟動(dòng)時(shí)會(huì)poll一條 JIT 就緒信號(hào)管道、最多等 5 秒拿不到就降級(jí)為無(wú) JIT 模式內(nèi)存限額主 App 攜帶com.apple.developer.kernel.increased-memory-limit權(quán)限Reynard.entitlements父進(jìn)程在子進(jìn)程起來(lái)后用 Utils.m 里的updateJetsamControl()單獨(dú)調(diào)高子進(jìn)程的 Jetsam 內(nèi)存上限避免內(nèi)容進(jìn)程被輕易殺掉。另外補(bǔ)丁還消費(fèi)了一個(gè) Documents 沙箱擴(kuò)展MOZ_DOCUMENTS_SANDBOX_EXTENSION讓內(nèi)容進(jìn)程能打開下載的文件——這是從 WebKit 的做法移植過(guò)來(lái)的。順帶一提OpenIn 分享擴(kuò)展除了「進(jìn)程容器」App Extension 還被用在產(chǎn)品功能上Extensions/OpenIn/ 是一個(gè)分享擴(kuò)展從系統(tǒng)分享面板拿到鏈接后拼成reynard://open深鏈喚回主 App邏輯見 OpenInViewController.swift讓「用 Reynard 打開」出現(xiàn)在所有分享列表里。構(gòu)建與打包腳本 雙 Scheme拉取引擎并打補(bǔ)丁./tools/development/update-gecko.sh、./tools/development/apply-patches.sh補(bǔ)丁由 create-patches.sh 生成源碼在 engine/firefox/Xcode 工程 Reynard.xcodeproj 里有兩個(gè)共享 SchemeReynard與Reynard Helper見 xcshareddata/xcschemes/Helper 目標(biāo)的鏈接參數(shù)里顯式加了-Wl,-u,_NSExtensionMain保證擴(kuò)展入口符號(hào)不被裁剪發(fā)布腳本 tools/release/create-ipa.sh 負(fù)責(zé)產(chǎn)出可側(cè)載的 IPA。如果想在本地復(fù)現(xiàn)先克隆倉(cāng)庫(kù)git clone --recursive https://gitcode.com/gh_mirrors/re/reynard-browser再按上面順序執(zhí)行即可??偨Y(jié)三招拆解 iOS 多進(jìn)程瀏覽器層次方案對(duì)應(yīng)代碼進(jìn)程隔離復(fù)用 App Extension 的沙箱與啟動(dòng)機(jī)制_MultipleInstances支持多子進(jìn)程Helper/Info.plist進(jìn)程間通信NSXPC 握手取 endpoint底層xpc_connection_t直接喂給 Gecko IPCNSExtensionUtils.mm、Helper.swift能力補(bǔ)齊DDI 配對(duì)/ptrace 啟 JIT、 Jetsam 限額、沙箱擴(kuò)展JITController.swift、IOSBootstrap.mmReynard 的思路其實(shí)很「取巧」它沒(méi)有對(duì)抗 iOS 的進(jìn)程模型而是把 Apple 為「App Extension」設(shè)計(jì)的隔離機(jī)制反向借用為「Gecko 子進(jìn)程的合法容器」再用 XPC 把引擎原生 IPC 無(wú)縫接上。這正是這個(gè)項(xiàng)目對(duì)「非 WebKit 瀏覽器能否落地 iOS」最有價(jià)值的回答。 ?贊分享【免費(fèi)下載鏈接】reynard-browserAn experimental Gecko-based web browser for iOS 13.項(xiàng)目地址https://gitcode.com/gh_mirrors/re/reynard-browser點(diǎn)擊查看免費(fèi)下載相關(guān)推薦為什么 Reynard Browser 拒絕證書簽名iOS 旁載限制與 App Extension 架構(gòu)深度解析為什么 Reynard Browser 拒絕證書簽名iOS 旁載限制與 App Extension 架構(gòu)深度解析 Reynard Browser 是目前 iO智能求職助手Boss Show Time招聘時(shí)間插件精準(zhǔn)把握就業(yè)時(shí)機(jī)智能求職助手Boss Show Time招聘時(shí)間插件精準(zhǔn)把握就業(yè)時(shí)機(jī) Boss Show Time是一款專為求職者設(shè)計(jì)的Chrome瀏覽器插件能夠 實(shí)時(shí)展示前端Reynard Browser 如何繞開 iOS JIT 禁令DDI 配對(duì)與 ptrace 雙通道機(jī)制深度拆解Reynard Browser 如何繞開 iOS JIT 禁令DDI 配對(duì)與 ptrace 雙通道機(jī)制深度拆解 Reynard Browser 是一款運(yùn)行在創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考