- commit
- 818272a
- parent
- 9674c30
- author
- im_wower
- date
- 2026-04-02 17:49:36 +0800 CST
feat: scaffold safari extension host shell
32 files changed,
+2149,
-0
Raw patch view.
1diff --git a/plugins/baa-safari/README.md b/plugins/baa-safari/README.md
2new file mode 100644
3index 0000000000000000000000000000000000000000..71d3d52756def3e4ed0c75a100ed527a1f95045f
4--- /dev/null
5+++ b/plugins/baa-safari/README.md
6@@ -0,0 +1,69 @@
7+# BAA Safari
8+
9+`plugins/baa-safari/` 是 Safari Web Extension 的 Phase 1 基础壳。
10+
11+当前这一版只解决三件事:
12+
13+- 固定 Safari 插件目录和文件边界
14+- 固定最小 Xcode 宿主 App / Extension target 命名
15+- 固定 runtime 归属合同,避免后续把长期状态 owner 放错到 `background.js`
16+
17+## Runtime 合同
18+
19+Safari 首版明确保留 `controller.html + controller.js` 作为长期 runtime owner 的正式选项。
20+
21+- `controller.html`
22+ 预留为 WS、状态机、壳页路由和调试视图的长期容器
23+- `background.js`
24+ 只负责 bootstrap、打开/聚焦控制页、汇总轻量占位状态
25+- `content-script.js`
26+ 只负责页面入口和注入 `page-interceptor.js`
27+- `page-interceptor.js`
28+ 当前仅保留页面 world 占位钩子,不提前实现 final-message / proxy_delivery
29+
30+这和 Firefox 当前真实结构保持一致:不要把 `background.js` 误当成主 runtime。
31+
32+## 目录
33+
34+- [manifest.json](/Users/george/code/baa-conductor/plugins/baa-safari/manifest.json)
35+ Safari Web Extension 基础声明,首版只覆盖 Claude / ChatGPT / Gemini
36+- [controller.html](/Users/george/code/baa-conductor/plugins/baa-safari/controller.html)
37+ 控制页容器,明确 runtime owner 合同和启用步骤
38+- [controller.js](/Users/george/code/baa-conductor/plugins/baa-safari/controller.js)
39+ 控制页最小状态渲染
40+- [controller.css](/Users/george/code/baa-conductor/plugins/baa-safari/controller.css)
41+ 控制页样式
42+- [background.js](/Users/george/code/baa-conductor/plugins/baa-safari/background.js)
43+ bootstrap / tab orchestration / placeholder state fan-out
44+- [content-script.js](/Users/george/code/baa-conductor/plugins/baa-safari/content-script.js)
45+ AI 页面入口,占位注入 `page-interceptor.js`
46+- [page-interceptor.js](/Users/george/code/baa-conductor/plugins/baa-safari/page-interceptor.js)
47+ 页面 world 占位钩子
48+- [baa-safari-host](/Users/george/code/baa-conductor/plugins/baa-safari/baa-safari-host)
49+ Xcode 宿主 App 与 Safari Extension target
50+
51+## 本地构建
52+
53+```bash
54+cd /Users/george/code/baa-conductor
55+xcodebuild -project plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj -scheme BAASafariHost -configuration Debug build
56+```
57+
58+## 首次启用
59+
60+1. 先构建 [BAASafariHost.xcodeproj](/Users/george/code/baa-conductor/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj)。
61+2. 运行 `BAASafariHost.app`。
62+3. 打开 Safari。
63+4. 进入 `Safari -> Settings -> Extensions`。
64+5. 启用 `BAA Safari Extension`。
65+6. 在 `Website Access` 里给 `claude.ai`、`chatgpt.com` / `chat.openai.com`、`gemini.google.com` 授权。
66+7. 点击 Safari 工具栏里的扩展按钮,让 `background.js` 打开 [controller.html](/Users/george/code/baa-conductor/plugins/baa-safari/controller.html)。
67+
68+## 当前不做
69+
70+- 不在这一步接 WS
71+- 不在这一步接 final-message
72+- 不在这一步接 proxy_delivery
73+- 不在这一步改 conductor 后端
74+
75+后续任务会在这个稳定骨架上继续接线。
76diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/BAASafariHost_Extension.entitlements b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/BAASafariHost_Extension.entitlements
77new file mode 100644
78index 0000000000000000000000000000000000000000..f2ef3ae0265b40c475e8ef90e3a311c31786c594
79--- /dev/null
80+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/BAASafariHost_Extension.entitlements
81@@ -0,0 +1,10 @@
82+<?xml version="1.0" encoding="UTF-8"?>
83+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
84+<plist version="1.0">
85+<dict>
86+ <key>com.apple.security.app-sandbox</key>
87+ <true/>
88+ <key>com.apple.security.files.user-selected.read-only</key>
89+ <true/>
90+</dict>
91+</plist>
92diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/Info.plist b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/Info.plist
93new file mode 100644
94index 0000000000000000000000000000000000000000..9ee504dc51f3d3c54be0fb0038a89b13b2250107
95--- /dev/null
96+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/Info.plist
97@@ -0,0 +1,13 @@
98+<?xml version="1.0" encoding="UTF-8"?>
99+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
100+<plist version="1.0">
101+<dict>
102+ <key>NSExtension</key>
103+ <dict>
104+ <key>NSExtensionPointIdentifier</key>
105+ <string>com.apple.Safari.web-extension</string>
106+ <key>NSExtensionPrincipalClass</key>
107+ <string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string>
108+ </dict>
109+</dict>
110+</plist>
111diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/SafariWebExtensionHandler.swift b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/SafariWebExtensionHandler.swift
112new file mode 100644
113index 0000000000000000000000000000000000000000..12ffd9f1016a8d8b23f75a9851d70af46ece0569
114--- /dev/null
115+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost Extension/SafariWebExtensionHandler.swift
116@@ -0,0 +1,42 @@
117+//
118+// SafariWebExtensionHandler.swift
119+// BAASafariHost Extension
120+//
121+// Created by george on 2026/4/2.
122+//
123+
124+import SafariServices
125+import os.log
126+
127+class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
128+
129+ func beginRequest(with context: NSExtensionContext) {
130+ let request = context.inputItems.first as? NSExtensionItem
131+
132+ let profile: UUID?
133+ if #available(iOS 17.0, macOS 14.0, *) {
134+ profile = request?.userInfo?[SFExtensionProfileKey] as? UUID
135+ } else {
136+ profile = request?.userInfo?["profile"] as? UUID
137+ }
138+
139+ let message: Any?
140+ if #available(iOS 15.0, macOS 11.0, *) {
141+ message = request?.userInfo?[SFExtensionMessageKey]
142+ } else {
143+ message = request?.userInfo?["message"]
144+ }
145+
146+ os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none")
147+
148+ let response = NSExtensionItem()
149+ if #available(iOS 15.0, macOS 11.0, *) {
150+ response.userInfo = [ SFExtensionMessageKey: [ "echo": message ] ]
151+ } else {
152+ response.userInfo = [ "message": [ "echo": message ] ]
153+ }
154+
155+ context.completeRequest(returningItems: [ response ], completionHandler: nil)
156+ }
157+
158+}
159diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.pbxproj b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.pbxproj
160new file mode 100644
161index 0000000000000000000000000000000000000000..215d14bccf2d7171c2b6a4f76924f7bc65631ec4
162--- /dev/null
163+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.pbxproj
164@@ -0,0 +1,853 @@
165+// !$*UTF8*$!
166+{
167+ archiveVersion = 1;
168+ classes = {
169+ };
170+ objectVersion = 77;
171+ objects = {
172+
173+/* Begin PBXBuildFile section */
174+ 5EF028BD2F7E7177006A58E2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028BC2F7E7177006A58E2 /* AppDelegate.swift */; };
175+ 5EF028C12F7E7177006A58E2 /* Main.html in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028BF2F7E7177006A58E2 /* Main.html */; };
176+ 5EF028C32F7E7177006A58E2 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028C22F7E7177006A58E2 /* Icon.png */; };
177+ 5EF028C52F7E7177006A58E2 /* Style.css in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028C42F7E7177006A58E2 /* Style.css */; };
178+ 5EF028C72F7E7177006A58E2 /* Script.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028C62F7E7177006A58E2 /* Script.js */; };
179+ 5EF028C92F7E7177006A58E2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028C82F7E7177006A58E2 /* ViewController.swift */; };
180+ 5EF028CC2F7E7177006A58E2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028CA2F7E7177006A58E2 /* Main.storyboard */; };
181+ 5EF028CE2F7E7177006A58E2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EF028CD2F7E7177006A58E2 /* Assets.xcassets */; };
182+ 5EF028DB2F7E7177006A58E2 /* BAASafariHostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028DA2F7E7177006A58E2 /* BAASafariHostTests.swift */; };
183+ 5EF028E52F7E7177006A58E2 /* BAASafariHostUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028E42F7E7177006A58E2 /* BAASafariHostUITests.swift */; };
184+ 5EF028E72F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028E62F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift */; };
185+ 5EF028ED2F7E7177006A58E2 /* BAASafariHost Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
186+ 5EF028F22F7E7177006A58E2 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF028F12F7E7177006A58E2 /* SafariWebExtensionHandler.swift */; };
187+ 5EF0290E2F7E7178006A58E2 /* background.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029052F7E7178006A58E2 /* background.js */; };
188+ 5EF0290F2F7E7178006A58E2 /* controller.css in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029062F7E7178006A58E2 /* controller.css */; };
189+ 5EF029102F7E7178006A58E2 /* controller.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029072F7E7178006A58E2 /* controller.js */; };
190+ 5EF029112F7E7178006A58E2 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029082F7E7178006A58E2 /* README.md */; };
191+ 5EF029122F7E7178006A58E2 /* controller.html in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029092F7E7178006A58E2 /* controller.html */; };
192+ 5EF029132F7E7178006A58E2 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0290A2F7E7178006A58E2 /* manifest.json */; };
193+ 5EF029142F7E7178006A58E2 /* content-script.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0290B2F7E7178006A58E2 /* content-script.js */; };
194+ 5EF029162F7E7178006A58E2 /* page-interceptor.js in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0290D2F7E7178006A58E2 /* page-interceptor.js */; };
195+ 5EF029172F7E7178006A58E2 /* 48.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF029182F7E7178006A58E2 /* 48.png */; };
196+ 5EF029192F7E7178006A58E2 /* 96.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0291A2F7E7178006A58E2 /* 96.png */; };
197+ 5EF0291B2F7E7178006A58E2 /* 128.png in Resources */ = {isa = PBXBuildFile; fileRef = 5EF0291C2F7E7178006A58E2 /* 128.png */; };
198+/* End PBXBuildFile section */
199+
200+/* Begin PBXContainerItemProxy section */
201+ 5EF028D72F7E7177006A58E2 /* PBXContainerItemProxy */ = {
202+ isa = PBXContainerItemProxy;
203+ containerPortal = 5EF028B12F7E7177006A58E2 /* Project object */;
204+ proxyType = 1;
205+ remoteGlobalIDString = 5EF028B82F7E7177006A58E2;
206+ remoteInfo = BAASafariHost;
207+ };
208+ 5EF028E12F7E7177006A58E2 /* PBXContainerItemProxy */ = {
209+ isa = PBXContainerItemProxy;
210+ containerPortal = 5EF028B12F7E7177006A58E2 /* Project object */;
211+ proxyType = 1;
212+ remoteGlobalIDString = 5EF028B82F7E7177006A58E2;
213+ remoteInfo = BAASafariHost;
214+ };
215+ 5EF028EE2F7E7177006A58E2 /* PBXContainerItemProxy */ = {
216+ isa = PBXContainerItemProxy;
217+ containerPortal = 5EF028B12F7E7177006A58E2 /* Project object */;
218+ proxyType = 1;
219+ remoteGlobalIDString = 5EF028EB2F7E7177006A58E2;
220+ remoteInfo = "BAASafariHost Extension";
221+ };
222+/* End PBXContainerItemProxy section */
223+
224+/* Begin PBXCopyFilesBuildPhase section */
225+ 5EF028FA2F7E7177006A58E2 /* Embed Foundation Extensions */ = {
226+ isa = PBXCopyFilesBuildPhase;
227+ buildActionMask = 2147483647;
228+ dstPath = "";
229+ dstSubfolderSpec = 13;
230+ files = (
231+ 5EF028ED2F7E7177006A58E2 /* BAASafariHost Extension.appex in Embed Foundation Extensions */,
232+ );
233+ name = "Embed Foundation Extensions";
234+ runOnlyForDeploymentPostprocessing = 0;
235+ };
236+/* End PBXCopyFilesBuildPhase section */
237+
238+/* Begin PBXFileReference section */
239+ 5EF028B92F7E7177006A58E2 /* BAASafariHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BAASafariHost.app; sourceTree = BUILT_PRODUCTS_DIR; };
240+ 5EF028BC2F7E7177006A58E2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
241+ 5EF028C02F7E7177006A58E2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = Base; path = Base.lproj/Main.html; sourceTree = "<group>"; };
242+ 5EF028C22F7E7177006A58E2 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; };
243+ 5EF028C42F7E7177006A58E2 /* Style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = Style.css; sourceTree = "<group>"; };
244+ 5EF028C62F7E7177006A58E2 /* Script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Script.js; sourceTree = "<group>"; };
245+ 5EF028C82F7E7177006A58E2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
246+ 5EF028CB2F7E7177006A58E2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
247+ 5EF028CD2F7E7177006A58E2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
248+ 5EF028CF2F7E7177006A58E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
249+ 5EF028D02F7E7177006A58E2 /* BAASafariHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BAASafariHost.entitlements; sourceTree = "<group>"; };
250+ 5EF028D12F7E7177006A58E2 /* BAASafariHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BAASafariHost.entitlements; sourceTree = "<group>"; };
251+ 5EF028D62F7E7177006A58E2 /* BAASafariHostTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BAASafariHostTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
252+ 5EF028DA2F7E7177006A58E2 /* BAASafariHostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BAASafariHostTests.swift; sourceTree = "<group>"; };
253+ 5EF028E02F7E7177006A58E2 /* BAASafariHostUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BAASafariHostUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
254+ 5EF028E42F7E7177006A58E2 /* BAASafariHostUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BAASafariHostUITests.swift; sourceTree = "<group>"; };
255+ 5EF028E62F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BAASafariHostUITestsLaunchTests.swift; sourceTree = "<group>"; };
256+ 5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "BAASafariHost Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
257+ 5EF028F12F7E7177006A58E2 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; };
258+ 5EF028F32F7E7177006A58E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
259+ 5EF028F42F7E7177006A58E2 /* BAASafariHost_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BAASafariHost_Extension.entitlements; sourceTree = "<group>"; };
260+ 5EF029052F7E7178006A58E2 /* background.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = background.js; path = ../../background.js; sourceTree = "<group>"; };
261+ 5EF029062F7E7178006A58E2 /* controller.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; name = controller.css; path = ../../controller.css; sourceTree = "<group>"; };
262+ 5EF029072F7E7178006A58E2 /* controller.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = controller.js; path = ../../controller.js; sourceTree = "<group>"; };
263+ 5EF029082F7E7178006A58E2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = "<group>"; };
264+ 5EF029092F7E7178006A58E2 /* controller.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = controller.html; path = ../../controller.html; sourceTree = "<group>"; };
265+ 5EF0290A2F7E7178006A58E2 /* manifest.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = manifest.json; path = ../../manifest.json; sourceTree = "<group>"; };
266+ 5EF0290B2F7E7178006A58E2 /* content-script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = "content-script.js"; path = "../../content-script.js"; sourceTree = "<group>"; };
267+ 5EF0290D2F7E7178006A58E2 /* page-interceptor.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = "page-interceptor.js"; path = "../../page-interceptor.js"; sourceTree = "<group>"; };
268+ 5EF029182F7E7178006A58E2 /* 48.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 48.png; path = ../../icons/48.png; sourceTree = "<group>"; };
269+ 5EF0291A2F7E7178006A58E2 /* 96.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 96.png; path = ../../icons/96.png; sourceTree = "<group>"; };
270+ 5EF0291C2F7E7178006A58E2 /* 128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = 128.png; path = ../../icons/128.png; sourceTree = "<group>"; };
271+/* End PBXFileReference section */
272+
273+/* Begin PBXFrameworksBuildPhase section */
274+ 5EF028B62F7E7177006A58E2 /* Frameworks */ = {
275+ isa = PBXFrameworksBuildPhase;
276+ buildActionMask = 2147483647;
277+ files = (
278+ );
279+ runOnlyForDeploymentPostprocessing = 0;
280+ };
281+ 5EF028D32F7E7177006A58E2 /* Frameworks */ = {
282+ isa = PBXFrameworksBuildPhase;
283+ buildActionMask = 2147483647;
284+ files = (
285+ );
286+ runOnlyForDeploymentPostprocessing = 0;
287+ };
288+ 5EF028DD2F7E7177006A58E2 /* Frameworks */ = {
289+ isa = PBXFrameworksBuildPhase;
290+ buildActionMask = 2147483647;
291+ files = (
292+ );
293+ runOnlyForDeploymentPostprocessing = 0;
294+ };
295+ 5EF028E92F7E7177006A58E2 /* Frameworks */ = {
296+ isa = PBXFrameworksBuildPhase;
297+ buildActionMask = 2147483647;
298+ files = (
299+ );
300+ runOnlyForDeploymentPostprocessing = 0;
301+ };
302+/* End PBXFrameworksBuildPhase section */
303+
304+/* Begin PBXGroup section */
305+ 5EF028B02F7E7177006A58E2 = {
306+ isa = PBXGroup;
307+ children = (
308+ 5EF028BB2F7E7177006A58E2 /* BAASafariHost */,
309+ 5EF028D92F7E7177006A58E2 /* BAASafariHostTests */,
310+ 5EF028E32F7E7177006A58E2 /* BAASafariHostUITests */,
311+ 5EF028F02F7E7177006A58E2 /* BAASafariHost Extension */,
312+ 5EF028BA2F7E7177006A58E2 /* Products */,
313+ );
314+ sourceTree = "<group>";
315+ };
316+ 5EF028BA2F7E7177006A58E2 /* Products */ = {
317+ isa = PBXGroup;
318+ children = (
319+ 5EF028B92F7E7177006A58E2 /* BAASafariHost.app */,
320+ 5EF028D62F7E7177006A58E2 /* BAASafariHostTests.xctest */,
321+ 5EF028E02F7E7177006A58E2 /* BAASafariHostUITests.xctest */,
322+ 5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */,
323+ );
324+ name = Products;
325+ sourceTree = "<group>";
326+ };
327+ 5EF028BB2F7E7177006A58E2 /* BAASafariHost */ = {
328+ isa = PBXGroup;
329+ children = (
330+ 5EF028BC2F7E7177006A58E2 /* AppDelegate.swift */,
331+ 5EF028C82F7E7177006A58E2 /* ViewController.swift */,
332+ 5EF028CA2F7E7177006A58E2 /* Main.storyboard */,
333+ 5EF028CD2F7E7177006A58E2 /* Assets.xcassets */,
334+ 5EF028CF2F7E7177006A58E2 /* Info.plist */,
335+ 5EF028D02F7E7177006A58E2 /* BAASafariHost.entitlements */,
336+ 5EF028D12F7E7177006A58E2 /* BAASafariHost.entitlements */,
337+ 5EF028BE2F7E7177006A58E2 /* Resources */,
338+ );
339+ path = BAASafariHost;
340+ sourceTree = "<group>";
341+ };
342+ 5EF028BE2F7E7177006A58E2 /* Resources */ = {
343+ isa = PBXGroup;
344+ children = (
345+ 5EF028BF2F7E7177006A58E2 /* Main.html */,
346+ 5EF028C22F7E7177006A58E2 /* Icon.png */,
347+ 5EF028C42F7E7177006A58E2 /* Style.css */,
348+ 5EF028C62F7E7177006A58E2 /* Script.js */,
349+ );
350+ path = Resources;
351+ sourceTree = "<group>";
352+ };
353+ 5EF028D92F7E7177006A58E2 /* BAASafariHostTests */ = {
354+ isa = PBXGroup;
355+ children = (
356+ 5EF028DA2F7E7177006A58E2 /* BAASafariHostTests.swift */,
357+ );
358+ path = BAASafariHostTests;
359+ sourceTree = "<group>";
360+ };
361+ 5EF028E32F7E7177006A58E2 /* BAASafariHostUITests */ = {
362+ isa = PBXGroup;
363+ children = (
364+ 5EF028E42F7E7177006A58E2 /* BAASafariHostUITests.swift */,
365+ 5EF028E62F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift */,
366+ );
367+ path = BAASafariHostUITests;
368+ sourceTree = "<group>";
369+ };
370+ 5EF028F02F7E7177006A58E2 /* BAASafariHost Extension */ = {
371+ isa = PBXGroup;
372+ children = (
373+ 5EF029042F7E7178006A58E2 /* Resources */,
374+ 5EF028F12F7E7177006A58E2 /* SafariWebExtensionHandler.swift */,
375+ 5EF028F32F7E7177006A58E2 /* Info.plist */,
376+ 5EF028F42F7E7177006A58E2 /* BAASafariHost_Extension.entitlements */,
377+ );
378+ path = "BAASafariHost Extension";
379+ sourceTree = "<group>";
380+ };
381+ 5EF029042F7E7178006A58E2 /* Resources */ = {
382+ isa = PBXGroup;
383+ children = (
384+ 5EF029052F7E7178006A58E2 /* background.js */,
385+ 5EF029062F7E7178006A58E2 /* controller.css */,
386+ 5EF029072F7E7178006A58E2 /* controller.js */,
387+ 5EF029082F7E7178006A58E2 /* README.md */,
388+ 5EF029092F7E7178006A58E2 /* controller.html */,
389+ 5EF0290A2F7E7178006A58E2 /* manifest.json */,
390+ 5EF0290B2F7E7178006A58E2 /* content-script.js */,
391+ 5EF0290D2F7E7178006A58E2 /* page-interceptor.js */,
392+ 5EF029182F7E7178006A58E2 /* 48.png */,
393+ 5EF0291A2F7E7178006A58E2 /* 96.png */,
394+ 5EF0291C2F7E7178006A58E2 /* 128.png */,
395+ );
396+ name = Resources;
397+ path = "BAASafariHost Extension";
398+ sourceTree = SOURCE_ROOT;
399+ };
400+/* End PBXGroup section */
401+
402+/* Begin PBXNativeTarget section */
403+ 5EF028B82F7E7177006A58E2 /* BAASafariHost */ = {
404+ isa = PBXNativeTarget;
405+ buildConfigurationList = 5EF028FB2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost" */;
406+ buildPhases = (
407+ 5EF028B52F7E7177006A58E2 /* Sources */,
408+ 5EF028B62F7E7177006A58E2 /* Frameworks */,
409+ 5EF028B72F7E7177006A58E2 /* Resources */,
410+ 5EF028FA2F7E7177006A58E2 /* Embed Foundation Extensions */,
411+ );
412+ buildRules = (
413+ );
414+ dependencies = (
415+ 5EF028EF2F7E7177006A58E2 /* PBXTargetDependency */,
416+ );
417+ name = BAASafariHost;
418+ packageProductDependencies = (
419+ );
420+ productName = BAASafariHost;
421+ productReference = 5EF028B92F7E7177006A58E2 /* BAASafariHost.app */;
422+ productType = "com.apple.product-type.application";
423+ };
424+ 5EF028D52F7E7177006A58E2 /* BAASafariHostTests */ = {
425+ isa = PBXNativeTarget;
426+ buildConfigurationList = 5EF028FE2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostTests" */;
427+ buildPhases = (
428+ 5EF028D22F7E7177006A58E2 /* Sources */,
429+ 5EF028D32F7E7177006A58E2 /* Frameworks */,
430+ 5EF028D42F7E7177006A58E2 /* Resources */,
431+ );
432+ buildRules = (
433+ );
434+ dependencies = (
435+ 5EF028D82F7E7177006A58E2 /* PBXTargetDependency */,
436+ );
437+ name = BAASafariHostTests;
438+ packageProductDependencies = (
439+ );
440+ productName = BAASafariHostTests;
441+ productReference = 5EF028D62F7E7177006A58E2 /* BAASafariHostTests.xctest */;
442+ productType = "com.apple.product-type.bundle.unit-test";
443+ };
444+ 5EF028DF2F7E7177006A58E2 /* BAASafariHostUITests */ = {
445+ isa = PBXNativeTarget;
446+ buildConfigurationList = 5EF029012F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostUITests" */;
447+ buildPhases = (
448+ 5EF028DC2F7E7177006A58E2 /* Sources */,
449+ 5EF028DD2F7E7177006A58E2 /* Frameworks */,
450+ 5EF028DE2F7E7177006A58E2 /* Resources */,
451+ );
452+ buildRules = (
453+ );
454+ dependencies = (
455+ 5EF028E22F7E7177006A58E2 /* PBXTargetDependency */,
456+ );
457+ name = BAASafariHostUITests;
458+ packageProductDependencies = (
459+ );
460+ productName = BAASafariHostUITests;
461+ productReference = 5EF028E02F7E7177006A58E2 /* BAASafariHostUITests.xctest */;
462+ productType = "com.apple.product-type.bundle.ui-testing";
463+ };
464+ 5EF028EB2F7E7177006A58E2 /* BAASafariHost Extension */ = {
465+ isa = PBXNativeTarget;
466+ buildConfigurationList = 5EF028F72F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost Extension" */;
467+ buildPhases = (
468+ 5EF028E82F7E7177006A58E2 /* Sources */,
469+ 5EF028E92F7E7177006A58E2 /* Frameworks */,
470+ 5EF028EA2F7E7177006A58E2 /* Resources */,
471+ );
472+ buildRules = (
473+ );
474+ dependencies = (
475+ );
476+ name = "BAASafariHost Extension";
477+ packageProductDependencies = (
478+ );
479+ productName = "BAASafariHost Extension";
480+ productReference = 5EF028EC2F7E7177006A58E2 /* BAASafariHost Extension.appex */;
481+ productType = "com.apple.product-type.app-extension";
482+ };
483+/* End PBXNativeTarget section */
484+
485+/* Begin PBXProject section */
486+ 5EF028B12F7E7177006A58E2 /* Project object */ = {
487+ isa = PBXProject;
488+ attributes = {
489+ BuildIndependentTargetsInParallel = 1;
490+ LastSwiftUpdateCheck = 1620;
491+ LastUpgradeCheck = 1620;
492+ TargetAttributes = {
493+ 5EF028B82F7E7177006A58E2 = {
494+ CreatedOnToolsVersion = 16.2;
495+ };
496+ 5EF028D52F7E7177006A58E2 = {
497+ CreatedOnToolsVersion = 16.2;
498+ TestTargetID = 5EF028B82F7E7177006A58E2;
499+ };
500+ 5EF028DF2F7E7177006A58E2 = {
501+ CreatedOnToolsVersion = 16.2;
502+ TestTargetID = 5EF028B82F7E7177006A58E2;
503+ };
504+ 5EF028EB2F7E7177006A58E2 = {
505+ CreatedOnToolsVersion = 16.2;
506+ };
507+ };
508+ };
509+ buildConfigurationList = 5EF028B42F7E7177006A58E2 /* Build configuration list for PBXProject "BAASafariHost" */;
510+ developmentRegion = en;
511+ hasScannedForEncodings = 0;
512+ knownRegions = (
513+ en,
514+ Base,
515+ );
516+ mainGroup = 5EF028B02F7E7177006A58E2;
517+ minimizedProjectReferenceProxies = 1;
518+ preferredProjectObjectVersion = 77;
519+ productRefGroup = 5EF028BA2F7E7177006A58E2 /* Products */;
520+ projectDirPath = "";
521+ projectRoot = "";
522+ targets = (
523+ 5EF028B82F7E7177006A58E2 /* BAASafariHost */,
524+ 5EF028D52F7E7177006A58E2 /* BAASafariHostTests */,
525+ 5EF028DF2F7E7177006A58E2 /* BAASafariHostUITests */,
526+ 5EF028EB2F7E7177006A58E2 /* BAASafariHost Extension */,
527+ );
528+ };
529+/* End PBXProject section */
530+
531+/* Begin PBXResourcesBuildPhase section */
532+ 5EF028B72F7E7177006A58E2 /* Resources */ = {
533+ isa = PBXResourcesBuildPhase;
534+ buildActionMask = 2147483647;
535+ files = (
536+ 5EF028C32F7E7177006A58E2 /* Icon.png in Resources */,
537+ 5EF028CC2F7E7177006A58E2 /* Main.storyboard in Resources */,
538+ 5EF028C72F7E7177006A58E2 /* Script.js in Resources */,
539+ 5EF028C12F7E7177006A58E2 /* Main.html in Resources */,
540+ 5EF028CE2F7E7177006A58E2 /* Assets.xcassets in Resources */,
541+ 5EF028C52F7E7177006A58E2 /* Style.css in Resources */,
542+ );
543+ runOnlyForDeploymentPostprocessing = 0;
544+ };
545+ 5EF028D42F7E7177006A58E2 /* Resources */ = {
546+ isa = PBXResourcesBuildPhase;
547+ buildActionMask = 2147483647;
548+ files = (
549+ );
550+ runOnlyForDeploymentPostprocessing = 0;
551+ };
552+ 5EF028DE2F7E7177006A58E2 /* Resources */ = {
553+ isa = PBXResourcesBuildPhase;
554+ buildActionMask = 2147483647;
555+ files = (
556+ );
557+ runOnlyForDeploymentPostprocessing = 0;
558+ };
559+ 5EF028EA2F7E7177006A58E2 /* Resources */ = {
560+ isa = PBXResourcesBuildPhase;
561+ buildActionMask = 2147483647;
562+ files = (
563+ 5EF029142F7E7178006A58E2 /* content-script.js in Resources */,
564+ 5EF0290E2F7E7178006A58E2 /* background.js in Resources */,
565+ 5EF029102F7E7178006A58E2 /* controller.js in Resources */,
566+ 5EF029122F7E7178006A58E2 /* controller.html in Resources */,
567+ 5EF029162F7E7178006A58E2 /* page-interceptor.js in Resources */,
568+ 5EF029172F7E7178006A58E2 /* 48.png in Resources */,
569+ 5EF029192F7E7178006A58E2 /* 96.png in Resources */,
570+ 5EF0291B2F7E7178006A58E2 /* 128.png in Resources */,
571+ 5EF029112F7E7178006A58E2 /* README.md in Resources */,
572+ 5EF029132F7E7178006A58E2 /* manifest.json in Resources */,
573+ 5EF0290F2F7E7178006A58E2 /* controller.css in Resources */,
574+ );
575+ runOnlyForDeploymentPostprocessing = 0;
576+ };
577+/* End PBXResourcesBuildPhase section */
578+
579+/* Begin PBXSourcesBuildPhase section */
580+ 5EF028B52F7E7177006A58E2 /* Sources */ = {
581+ isa = PBXSourcesBuildPhase;
582+ buildActionMask = 2147483647;
583+ files = (
584+ 5EF028C92F7E7177006A58E2 /* ViewController.swift in Sources */,
585+ 5EF028BD2F7E7177006A58E2 /* AppDelegate.swift in Sources */,
586+ );
587+ runOnlyForDeploymentPostprocessing = 0;
588+ };
589+ 5EF028D22F7E7177006A58E2 /* Sources */ = {
590+ isa = PBXSourcesBuildPhase;
591+ buildActionMask = 2147483647;
592+ files = (
593+ 5EF028DB2F7E7177006A58E2 /* BAASafariHostTests.swift in Sources */,
594+ );
595+ runOnlyForDeploymentPostprocessing = 0;
596+ };
597+ 5EF028DC2F7E7177006A58E2 /* Sources */ = {
598+ isa = PBXSourcesBuildPhase;
599+ buildActionMask = 2147483647;
600+ files = (
601+ 5EF028E52F7E7177006A58E2 /* BAASafariHostUITests.swift in Sources */,
602+ 5EF028E72F7E7177006A58E2 /* BAASafariHostUITestsLaunchTests.swift in Sources */,
603+ );
604+ runOnlyForDeploymentPostprocessing = 0;
605+ };
606+ 5EF028E82F7E7177006A58E2 /* Sources */ = {
607+ isa = PBXSourcesBuildPhase;
608+ buildActionMask = 2147483647;
609+ files = (
610+ 5EF028F22F7E7177006A58E2 /* SafariWebExtensionHandler.swift in Sources */,
611+ );
612+ runOnlyForDeploymentPostprocessing = 0;
613+ };
614+/* End PBXSourcesBuildPhase section */
615+
616+/* Begin PBXTargetDependency section */
617+ 5EF028D82F7E7177006A58E2 /* PBXTargetDependency */ = {
618+ isa = PBXTargetDependency;
619+ target = 5EF028B82F7E7177006A58E2 /* BAASafariHost */;
620+ targetProxy = 5EF028D72F7E7177006A58E2 /* PBXContainerItemProxy */;
621+ };
622+ 5EF028E22F7E7177006A58E2 /* PBXTargetDependency */ = {
623+ isa = PBXTargetDependency;
624+ target = 5EF028B82F7E7177006A58E2 /* BAASafariHost */;
625+ targetProxy = 5EF028E12F7E7177006A58E2 /* PBXContainerItemProxy */;
626+ };
627+ 5EF028EF2F7E7177006A58E2 /* PBXTargetDependency */ = {
628+ isa = PBXTargetDependency;
629+ target = 5EF028EB2F7E7177006A58E2 /* BAASafariHost Extension */;
630+ targetProxy = 5EF028EE2F7E7177006A58E2 /* PBXContainerItemProxy */;
631+ };
632+/* End PBXTargetDependency section */
633+
634+/* Begin PBXVariantGroup section */
635+ 5EF028BF2F7E7177006A58E2 /* Main.html */ = {
636+ isa = PBXVariantGroup;
637+ children = (
638+ 5EF028C02F7E7177006A58E2 /* Base */,
639+ );
640+ name = Main.html;
641+ sourceTree = "<group>";
642+ };
643+ 5EF028CA2F7E7177006A58E2 /* Main.storyboard */ = {
644+ isa = PBXVariantGroup;
645+ children = (
646+ 5EF028CB2F7E7177006A58E2 /* Base */,
647+ );
648+ name = Main.storyboard;
649+ sourceTree = "<group>";
650+ };
651+/* End PBXVariantGroup section */
652+
653+/* Begin XCBuildConfiguration section */
654+ 5EF028F52F7E7177006A58E2 /* Debug */ = {
655+ isa = XCBuildConfiguration;
656+ buildSettings = {
657+ ALWAYS_SEARCH_USER_PATHS = NO;
658+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
659+ CLANG_ANALYZER_NONNULL = YES;
660+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
661+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
662+ CLANG_ENABLE_MODULES = YES;
663+ CLANG_ENABLE_OBJC_ARC = YES;
664+ CLANG_ENABLE_OBJC_WEAK = YES;
665+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
666+ CLANG_WARN_BOOL_CONVERSION = YES;
667+ CLANG_WARN_COMMA = YES;
668+ CLANG_WARN_CONSTANT_CONVERSION = YES;
669+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
670+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
671+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
672+ CLANG_WARN_EMPTY_BODY = YES;
673+ CLANG_WARN_ENUM_CONVERSION = YES;
674+ CLANG_WARN_INFINITE_RECURSION = YES;
675+ CLANG_WARN_INT_CONVERSION = YES;
676+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
677+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
678+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
679+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
680+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
681+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
682+ CLANG_WARN_STRICT_PROTOTYPES = YES;
683+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
684+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
685+ CLANG_WARN_UNREACHABLE_CODE = YES;
686+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
687+ COPY_PHASE_STRIP = NO;
688+ DEBUG_INFORMATION_FORMAT = dwarf;
689+ ENABLE_STRICT_OBJC_MSGSEND = YES;
690+ ENABLE_TESTABILITY = YES;
691+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
692+ GCC_C_LANGUAGE_STANDARD = gnu17;
693+ GCC_DYNAMIC_NO_PIC = NO;
694+ GCC_NO_COMMON_BLOCKS = YES;
695+ GCC_OPTIMIZATION_LEVEL = 0;
696+ GCC_PREPROCESSOR_DEFINITIONS = (
697+ "DEBUG=1",
698+ "$(inherited)",
699+ );
700+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
701+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
702+ GCC_WARN_UNDECLARED_SELECTOR = YES;
703+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
704+ GCC_WARN_UNUSED_FUNCTION = YES;
705+ GCC_WARN_UNUSED_VARIABLE = YES;
706+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
707+ MACOSX_DEPLOYMENT_TARGET = 15.1;
708+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
709+ MTL_FAST_MATH = YES;
710+ ONLY_ACTIVE_ARCH = YES;
711+ SDKROOT = macosx;
712+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
713+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
714+ };
715+ name = Debug;
716+ };
717+ 5EF028F62F7E7177006A58E2 /* Release */ = {
718+ isa = XCBuildConfiguration;
719+ buildSettings = {
720+ ALWAYS_SEARCH_USER_PATHS = NO;
721+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
722+ CLANG_ANALYZER_NONNULL = YES;
723+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
724+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
725+ CLANG_ENABLE_MODULES = YES;
726+ CLANG_ENABLE_OBJC_ARC = YES;
727+ CLANG_ENABLE_OBJC_WEAK = YES;
728+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
729+ CLANG_WARN_BOOL_CONVERSION = YES;
730+ CLANG_WARN_COMMA = YES;
731+ CLANG_WARN_CONSTANT_CONVERSION = YES;
732+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
733+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
734+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
735+ CLANG_WARN_EMPTY_BODY = YES;
736+ CLANG_WARN_ENUM_CONVERSION = YES;
737+ CLANG_WARN_INFINITE_RECURSION = YES;
738+ CLANG_WARN_INT_CONVERSION = YES;
739+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
740+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
741+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
742+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
743+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
744+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
745+ CLANG_WARN_STRICT_PROTOTYPES = YES;
746+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
747+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
748+ CLANG_WARN_UNREACHABLE_CODE = YES;
749+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
750+ COPY_PHASE_STRIP = NO;
751+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
752+ ENABLE_NS_ASSERTIONS = NO;
753+ ENABLE_STRICT_OBJC_MSGSEND = YES;
754+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
755+ GCC_C_LANGUAGE_STANDARD = gnu17;
756+ GCC_NO_COMMON_BLOCKS = YES;
757+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
758+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
759+ GCC_WARN_UNDECLARED_SELECTOR = YES;
760+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
761+ GCC_WARN_UNUSED_FUNCTION = YES;
762+ GCC_WARN_UNUSED_VARIABLE = YES;
763+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
764+ MACOSX_DEPLOYMENT_TARGET = 15.1;
765+ MTL_ENABLE_DEBUG_INFO = NO;
766+ MTL_FAST_MATH = YES;
767+ SDKROOT = macosx;
768+ SWIFT_COMPILATION_MODE = wholemodule;
769+ };
770+ name = Release;
771+ };
772+ 5EF028F82F7E7177006A58E2 /* Debug */ = {
773+ isa = XCBuildConfiguration;
774+ buildSettings = {
775+ CODE_SIGN_ENTITLEMENTS = "BAASafariHost Extension/BAASafariHost_Extension.entitlements";
776+ CODE_SIGN_STYLE = Automatic;
777+ CURRENT_PROJECT_VERSION = 1;
778+ ENABLE_HARDENED_RUNTIME = YES;
779+ GENERATE_INFOPLIST_FILE = YES;
780+ INFOPLIST_FILE = "BAASafariHost Extension/Info.plist";
781+ INFOPLIST_KEY_CFBundleDisplayName = "BAASafariHost Extension";
782+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
783+ LD_RUNPATH_SEARCH_PATHS = (
784+ "$(inherited)",
785+ "@executable_path/../Frameworks",
786+ "@executable_path/../../../../Frameworks",
787+ );
788+ MACOSX_DEPLOYMENT_TARGET = 10.14;
789+ MARKETING_VERSION = 1.0;
790+ OTHER_LDFLAGS = (
791+ "-framework",
792+ SafariServices,
793+ );
794+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost.Extension;
795+ PRODUCT_NAME = "$(TARGET_NAME)";
796+ SKIP_INSTALL = YES;
797+ SWIFT_EMIT_LOC_STRINGS = YES;
798+ SWIFT_VERSION = 5.0;
799+ };
800+ name = Debug;
801+ };
802+ 5EF028F92F7E7177006A58E2 /* Release */ = {
803+ isa = XCBuildConfiguration;
804+ buildSettings = {
805+ CODE_SIGN_ENTITLEMENTS = "BAASafariHost Extension/BAASafariHost_Extension.entitlements";
806+ CODE_SIGN_STYLE = Automatic;
807+ CURRENT_PROJECT_VERSION = 1;
808+ ENABLE_HARDENED_RUNTIME = YES;
809+ GENERATE_INFOPLIST_FILE = YES;
810+ INFOPLIST_FILE = "BAASafariHost Extension/Info.plist";
811+ INFOPLIST_KEY_CFBundleDisplayName = "BAASafariHost Extension";
812+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
813+ LD_RUNPATH_SEARCH_PATHS = (
814+ "$(inherited)",
815+ "@executable_path/../Frameworks",
816+ "@executable_path/../../../../Frameworks",
817+ );
818+ MACOSX_DEPLOYMENT_TARGET = 10.14;
819+ MARKETING_VERSION = 1.0;
820+ OTHER_LDFLAGS = (
821+ "-framework",
822+ SafariServices,
823+ );
824+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost.Extension;
825+ PRODUCT_NAME = "$(TARGET_NAME)";
826+ SKIP_INSTALL = YES;
827+ SWIFT_EMIT_LOC_STRINGS = YES;
828+ SWIFT_VERSION = 5.0;
829+ };
830+ name = Release;
831+ };
832+ 5EF028FC2F7E7177006A58E2 /* Debug */ = {
833+ isa = XCBuildConfiguration;
834+ buildSettings = {
835+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
836+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
837+ CODE_SIGN_ENTITLEMENTS = BAASafariHost/BAASafariHost.entitlements;
838+ CODE_SIGN_STYLE = Automatic;
839+ COMBINE_HIDPI_IMAGES = YES;
840+ CURRENT_PROJECT_VERSION = 1;
841+ ENABLE_HARDENED_RUNTIME = YES;
842+ GENERATE_INFOPLIST_FILE = YES;
843+ INFOPLIST_FILE = BAASafariHost/Info.plist;
844+ INFOPLIST_KEY_CFBundleDisplayName = BAASafariHost;
845+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
846+ INFOPLIST_KEY_NSMainStoryboardFile = Main;
847+ INFOPLIST_KEY_NSPrincipalClass = NSApplication;
848+ LD_RUNPATH_SEARCH_PATHS = (
849+ "$(inherited)",
850+ "@executable_path/../Frameworks",
851+ );
852+ MACOSX_DEPLOYMENT_TARGET = 10.14;
853+ MARKETING_VERSION = 1.0;
854+ OTHER_LDFLAGS = (
855+ "-framework",
856+ SafariServices,
857+ "-framework",
858+ WebKit,
859+ );
860+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost;
861+ PRODUCT_NAME = "$(TARGET_NAME)";
862+ SWIFT_EMIT_LOC_STRINGS = YES;
863+ SWIFT_VERSION = 5.0;
864+ };
865+ name = Debug;
866+ };
867+ 5EF028FD2F7E7177006A58E2 /* Release */ = {
868+ isa = XCBuildConfiguration;
869+ buildSettings = {
870+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
871+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
872+ CODE_SIGN_ENTITLEMENTS = BAASafariHost/BAASafariHost.entitlements;
873+ CODE_SIGN_STYLE = Automatic;
874+ COMBINE_HIDPI_IMAGES = YES;
875+ CURRENT_PROJECT_VERSION = 1;
876+ ENABLE_HARDENED_RUNTIME = YES;
877+ GENERATE_INFOPLIST_FILE = YES;
878+ INFOPLIST_FILE = BAASafariHost/Info.plist;
879+ INFOPLIST_KEY_CFBundleDisplayName = BAASafariHost;
880+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
881+ INFOPLIST_KEY_NSMainStoryboardFile = Main;
882+ INFOPLIST_KEY_NSPrincipalClass = NSApplication;
883+ LD_RUNPATH_SEARCH_PATHS = (
884+ "$(inherited)",
885+ "@executable_path/../Frameworks",
886+ );
887+ MACOSX_DEPLOYMENT_TARGET = 10.14;
888+ MARKETING_VERSION = 1.0;
889+ OTHER_LDFLAGS = (
890+ "-framework",
891+ SafariServices,
892+ "-framework",
893+ WebKit,
894+ );
895+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHost;
896+ PRODUCT_NAME = "$(TARGET_NAME)";
897+ SWIFT_EMIT_LOC_STRINGS = YES;
898+ SWIFT_VERSION = 5.0;
899+ };
900+ name = Release;
901+ };
902+ 5EF028FF2F7E7177006A58E2 /* Debug */ = {
903+ isa = XCBuildConfiguration;
904+ buildSettings = {
905+ BUNDLE_LOADER = "$(TEST_HOST)";
906+ CODE_SIGN_STYLE = Automatic;
907+ CURRENT_PROJECT_VERSION = 1;
908+ GENERATE_INFOPLIST_FILE = YES;
909+ MACOSX_DEPLOYMENT_TARGET = 10.14;
910+ MARKETING_VERSION = 1.0;
911+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostTests;
912+ PRODUCT_NAME = "$(TARGET_NAME)";
913+ SWIFT_EMIT_LOC_STRINGS = NO;
914+ SWIFT_VERSION = 5.0;
915+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BAASafariHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BAASafariHost";
916+ };
917+ name = Debug;
918+ };
919+ 5EF029002F7E7177006A58E2 /* Release */ = {
920+ isa = XCBuildConfiguration;
921+ buildSettings = {
922+ BUNDLE_LOADER = "$(TEST_HOST)";
923+ CODE_SIGN_STYLE = Automatic;
924+ CURRENT_PROJECT_VERSION = 1;
925+ GENERATE_INFOPLIST_FILE = YES;
926+ MACOSX_DEPLOYMENT_TARGET = 10.14;
927+ MARKETING_VERSION = 1.0;
928+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostTests;
929+ PRODUCT_NAME = "$(TARGET_NAME)";
930+ SWIFT_EMIT_LOC_STRINGS = NO;
931+ SWIFT_VERSION = 5.0;
932+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BAASafariHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BAASafariHost";
933+ };
934+ name = Release;
935+ };
936+ 5EF029022F7E7177006A58E2 /* Debug */ = {
937+ isa = XCBuildConfiguration;
938+ buildSettings = {
939+ CODE_SIGN_STYLE = Automatic;
940+ CURRENT_PROJECT_VERSION = 1;
941+ GENERATE_INFOPLIST_FILE = YES;
942+ MARKETING_VERSION = 1.0;
943+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostUITests;
944+ PRODUCT_NAME = "$(TARGET_NAME)";
945+ SWIFT_EMIT_LOC_STRINGS = NO;
946+ SWIFT_VERSION = 5.0;
947+ TEST_TARGET_NAME = BAASafariHost;
948+ };
949+ name = Debug;
950+ };
951+ 5EF029032F7E7177006A58E2 /* Release */ = {
952+ isa = XCBuildConfiguration;
953+ buildSettings = {
954+ CODE_SIGN_STYLE = Automatic;
955+ CURRENT_PROJECT_VERSION = 1;
956+ GENERATE_INFOPLIST_FILE = YES;
957+ MARKETING_VERSION = 1.0;
958+ PRODUCT_BUNDLE_IDENTIFIER = com.baa.safari.BAASafariHostUITests;
959+ PRODUCT_NAME = "$(TARGET_NAME)";
960+ SWIFT_EMIT_LOC_STRINGS = NO;
961+ SWIFT_VERSION = 5.0;
962+ TEST_TARGET_NAME = BAASafariHost;
963+ };
964+ name = Release;
965+ };
966+/* End XCBuildConfiguration section */
967+
968+/* Begin XCConfigurationList section */
969+ 5EF028B42F7E7177006A58E2 /* Build configuration list for PBXProject "BAASafariHost" */ = {
970+ isa = XCConfigurationList;
971+ buildConfigurations = (
972+ 5EF028F52F7E7177006A58E2 /* Debug */,
973+ 5EF028F62F7E7177006A58E2 /* Release */,
974+ );
975+ defaultConfigurationIsVisible = 0;
976+ defaultConfigurationName = Release;
977+ };
978+ 5EF028F72F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost Extension" */ = {
979+ isa = XCConfigurationList;
980+ buildConfigurations = (
981+ 5EF028F82F7E7177006A58E2 /* Debug */,
982+ 5EF028F92F7E7177006A58E2 /* Release */,
983+ );
984+ defaultConfigurationIsVisible = 0;
985+ defaultConfigurationName = Release;
986+ };
987+ 5EF028FB2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHost" */ = {
988+ isa = XCConfigurationList;
989+ buildConfigurations = (
990+ 5EF028FC2F7E7177006A58E2 /* Debug */,
991+ 5EF028FD2F7E7177006A58E2 /* Release */,
992+ );
993+ defaultConfigurationIsVisible = 0;
994+ defaultConfigurationName = Release;
995+ };
996+ 5EF028FE2F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostTests" */ = {
997+ isa = XCConfigurationList;
998+ buildConfigurations = (
999+ 5EF028FF2F7E7177006A58E2 /* Debug */,
1000+ 5EF029002F7E7177006A58E2 /* Release */,
1001+ );
1002+ defaultConfigurationIsVisible = 0;
1003+ defaultConfigurationName = Release;
1004+ };
1005+ 5EF029012F7E7177006A58E2 /* Build configuration list for PBXNativeTarget "BAASafariHostUITests" */ = {
1006+ isa = XCConfigurationList;
1007+ buildConfigurations = (
1008+ 5EF029022F7E7177006A58E2 /* Debug */,
1009+ 5EF029032F7E7177006A58E2 /* Release */,
1010+ );
1011+ defaultConfigurationIsVisible = 0;
1012+ defaultConfigurationName = Release;
1013+ };
1014+/* End XCConfigurationList section */
1015+ };
1016+ rootObject = 5EF028B12F7E7177006A58E2 /* Project object */;
1017+}
1018diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata
1019new file mode 100644
1020index 0000000000000000000000000000000000000000..919434a6254f0e9651f402737811be6634a03e9c
1021--- /dev/null
1022+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost.xcodeproj/project.xcworkspace/contents.xcworkspacedata
1023@@ -0,0 +1,7 @@
1024+<?xml version="1.0" encoding="UTF-8"?>
1025+<Workspace
1026+ version = "1.0">
1027+ <FileRef
1028+ location = "self:">
1029+ </FileRef>
1030+</Workspace>
1031diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/AppDelegate.swift b/plugins/baa-safari/baa-safari-host/BAASafariHost/AppDelegate.swift
1032new file mode 100644
1033index 0000000000000000000000000000000000000000..b75d6d6405b5fd844d648740581701f2e06f997a
1034--- /dev/null
1035+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/AppDelegate.swift
1036@@ -0,0 +1,21 @@
1037+//
1038+// AppDelegate.swift
1039+// BAASafariHost
1040+//
1041+// Created by george on 2026/4/2.
1042+//
1043+
1044+import Cocoa
1045+
1046+@main
1047+class AppDelegate: NSObject, NSApplicationDelegate {
1048+
1049+ func applicationDidFinishLaunching(_ notification: Notification) {
1050+ // Override point for customization after application launch.
1051+ }
1052+
1053+ func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
1054+ return true
1055+ }
1056+
1057+}
1058diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AccentColor.colorset/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AccentColor.colorset/Contents.json
1059new file mode 100644
1060index 0000000000000000000000000000000000000000..eb8789700816459c1e1480e0b34781d9fb78a1ca
1061--- /dev/null
1062+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AccentColor.colorset/Contents.json
1063@@ -0,0 +1,11 @@
1064+{
1065+ "colors" : [
1066+ {
1067+ "idiom" : "universal"
1068+ }
1069+ ],
1070+ "info" : {
1071+ "author" : "xcode",
1072+ "version" : 1
1073+ }
1074+}
1075diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AppIcon.appiconset/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AppIcon.appiconset/Contents.json
1076new file mode 100644
1077index 0000000000000000000000000000000000000000..3f00db43ec3c8b462759505d635dc5545d4e8e50
1078--- /dev/null
1079+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/AppIcon.appiconset/Contents.json
1080@@ -0,0 +1,58 @@
1081+{
1082+ "images" : [
1083+ {
1084+ "idiom" : "mac",
1085+ "scale" : "1x",
1086+ "size" : "16x16"
1087+ },
1088+ {
1089+ "idiom" : "mac",
1090+ "scale" : "2x",
1091+ "size" : "16x16"
1092+ },
1093+ {
1094+ "idiom" : "mac",
1095+ "scale" : "1x",
1096+ "size" : "32x32"
1097+ },
1098+ {
1099+ "idiom" : "mac",
1100+ "scale" : "2x",
1101+ "size" : "32x32"
1102+ },
1103+ {
1104+ "idiom" : "mac",
1105+ "scale" : "1x",
1106+ "size" : "128x128"
1107+ },
1108+ {
1109+ "idiom" : "mac",
1110+ "scale" : "2x",
1111+ "size" : "128x128"
1112+ },
1113+ {
1114+ "idiom" : "mac",
1115+ "scale" : "1x",
1116+ "size" : "256x256"
1117+ },
1118+ {
1119+ "idiom" : "mac",
1120+ "scale" : "2x",
1121+ "size" : "256x256"
1122+ },
1123+ {
1124+ "idiom" : "mac",
1125+ "scale" : "1x",
1126+ "size" : "512x512"
1127+ },
1128+ {
1129+ "idiom" : "mac",
1130+ "scale" : "2x",
1131+ "size" : "512x512"
1132+ }
1133+ ],
1134+ "info" : {
1135+ "author" : "xcode",
1136+ "version" : 1
1137+ }
1138+}
1139diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/Contents.json
1140new file mode 100644
1141index 0000000000000000000000000000000000000000..73c00596a7fca3f3d4bdd64053b69d86745f9e10
1142--- /dev/null
1143+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/Contents.json
1144@@ -0,0 +1,6 @@
1145+{
1146+ "info" : {
1147+ "author" : "xcode",
1148+ "version" : 1
1149+ }
1150+}
1151diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/LargeIcon.imageset/Contents.json b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/LargeIcon.imageset/Contents.json
1152new file mode 100644
1153index 0000000000000000000000000000000000000000..a19a5492203a8d30fc85ccc30497536a9500155d
1154--- /dev/null
1155+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Assets.xcassets/LargeIcon.imageset/Contents.json
1156@@ -0,0 +1,20 @@
1157+{
1158+ "images" : [
1159+ {
1160+ "idiom" : "universal",
1161+ "scale" : "1x"
1162+ },
1163+ {
1164+ "idiom" : "universal",
1165+ "scale" : "2x"
1166+ },
1167+ {
1168+ "idiom" : "universal",
1169+ "scale" : "3x"
1170+ }
1171+ ],
1172+ "info" : {
1173+ "author" : "xcode",
1174+ "version" : 1
1175+ }
1176+}
1177diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/BAASafariHost.entitlements b/plugins/baa-safari/baa-safari-host/BAASafariHost/BAASafariHost.entitlements
1178new file mode 100644
1179index 0000000000000000000000000000000000000000..625af03d99b2ea991b4a6dc8eed9db088e5afba1
1180--- /dev/null
1181+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/BAASafariHost.entitlements
1182@@ -0,0 +1,12 @@
1183+<?xml version="1.0" encoding="UTF-8"?>
1184+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1185+<plist version="1.0">
1186+<dict>
1187+ <key>com.apple.security.app-sandbox</key>
1188+ <true/>
1189+ <key>com.apple.security.files.user-selected.read-only</key>
1190+ <true/>
1191+ <key>com.apple.security.network.client</key>
1192+ <true/>
1193+</dict>
1194+</plist>
1195diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Base.lproj/Main.storyboard b/plugins/baa-safari/baa-safari-host/BAASafariHost/Base.lproj/Main.storyboard
1196new file mode 100644
1197index 0000000000000000000000000000000000000000..f31b8241388f186e0882183ea04dbe6af071083a
1198--- /dev/null
1199+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Base.lproj/Main.storyboard
1200@@ -0,0 +1,124 @@
1201+<?xml version="1.0" encoding="UTF-8"?>
1202+<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19085" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
1203+ <dependencies>
1204+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19085"/>
1205+ <plugIn identifier="com.apple.WebKit2IBPlugin" version="19085"/>
1206+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
1207+ </dependencies>
1208+ <scenes>
1209+ <!--Application-->
1210+ <scene sceneID="JPo-4y-FX3">
1211+ <objects>
1212+ <application id="hnw-xV-0zn" sceneMemberID="viewController">
1213+ <menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
1214+ <items>
1215+ <menuItem title="BAASafariHost" id="1Xt-HY-uBw">
1216+ <modifierMask key="keyEquivalentModifierMask"/>
1217+ <menu key="submenu" title="BAASafariHost" systemMenu="apple" id="uQy-DD-JDr">
1218+ <items>
1219+ <menuItem title="About BAASafariHost" id="5kV-Vb-QxS">
1220+ <modifierMask key="keyEquivalentModifierMask"/>
1221+ <connections>
1222+ <action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
1223+ </connections>
1224+ </menuItem>
1225+ <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
1226+ <menuItem title="Hide BAASafariHost" keyEquivalent="h" id="Olw-nP-bQN">
1227+ <connections>
1228+ <action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
1229+ </connections>
1230+ </menuItem>
1231+ <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
1232+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
1233+ <connections>
1234+ <action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
1235+ </connections>
1236+ </menuItem>
1237+ <menuItem title="Show All" id="Kd2-mp-pUS">
1238+ <modifierMask key="keyEquivalentModifierMask"/>
1239+ <connections>
1240+ <action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
1241+ </connections>
1242+ </menuItem>
1243+ <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
1244+ <menuItem title="Quit BAASafariHost" keyEquivalent="q" id="4sb-4s-VLi">
1245+ <connections>
1246+ <action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
1247+ </connections>
1248+ </menuItem>
1249+ </items>
1250+ </menu>
1251+ </menuItem>
1252+ <menuItem title="Help" id="wpr-3q-Mcd">
1253+ <modifierMask key="keyEquivalentModifierMask"/>
1254+ <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
1255+ <items>
1256+ <menuItem title="BAASafariHost Help" keyEquivalent="?" id="FKE-Sm-Kum">
1257+ <connections>
1258+ <action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
1259+ </connections>
1260+ </menuItem>
1261+ </items>
1262+ </menu>
1263+ </menuItem>
1264+ </items>
1265+ </menu>
1266+ <connections>
1267+ <outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
1268+ </connections>
1269+ </application>
1270+ <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target"/>
1271+ <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
1272+ <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
1273+ </objects>
1274+ <point key="canvasLocation" x="76" y="-134"/>
1275+ </scene>
1276+ <!--Window Controller-->
1277+ <scene sceneID="R2V-B0-nI4">
1278+ <objects>
1279+ <windowController showSeguePresentationStyle="single" id="B8D-0N-5wS" sceneMemberID="viewController">
1280+ <window key="window" title="BAASafariHost" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" animationBehavior="default" id="IQv-IB-iLA">
1281+ <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
1282+ <windowCollectionBehavior key="collectionBehavior" fullScreenNone="YES"/>
1283+ <rect key="contentRect" x="196" y="240" width="425" height="325"/>
1284+ <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
1285+ <connections>
1286+ <outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
1287+ </connections>
1288+ </window>
1289+ <connections>
1290+ <segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
1291+ </connections>
1292+ </windowController>
1293+ <customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
1294+ </objects>
1295+ <point key="canvasLocation" x="75" y="250"/>
1296+ </scene>
1297+ <!--View Controller-->
1298+ <scene sceneID="hIz-AP-VOD">
1299+ <objects>
1300+ <viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
1301+ <view key="view" id="m2S-Jp-Qdl">
1302+ <rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
1303+ <autoresizingMask key="autoresizingMask"/>
1304+ <subviews>
1305+ <wkWebView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eOr-cG-IQY">
1306+ <rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
1307+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
1308+ <wkWebViewConfiguration key="configuration">
1309+ <audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
1310+ <wkPreferences key="preferences"/>
1311+ </wkWebViewConfiguration>
1312+ </wkWebView>
1313+ </subviews>
1314+ </view>
1315+ <connections>
1316+ <outlet property="webView" destination="eOr-cG-IQY" id="GFe-mU-dBY"/>
1317+ </connections>
1318+ </viewController>
1319+ <customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
1320+ </objects>
1321+ <point key="canvasLocation" x="75" y="655"/>
1322+ </scene>
1323+ </scenes>
1324+</document>
1325diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Info.plist b/plugins/baa-safari/baa-safari-host/BAASafariHost/Info.plist
1326new file mode 100644
1327index 0000000000000000000000000000000000000000..716d66f1ba524566386509a943210e39fc8e4dd0
1328--- /dev/null
1329+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Info.plist
1330@@ -0,0 +1,8 @@
1331+<?xml version="1.0" encoding="UTF-8"?>
1332+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1333+<plist version="1.0">
1334+<dict>
1335+ <key>SFSafariWebExtensionConverterVersion</key>
1336+ <string>16.2</string>
1337+</dict>
1338+</plist>
1339diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Base.lproj/Main.html b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Base.lproj/Main.html
1340new file mode 100644
1341index 0000000000000000000000000000000000000000..e591d7054e219cc6e52a6744eac938dd88e4bfa6
1342--- /dev/null
1343+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Base.lproj/Main.html
1344@@ -0,0 +1,31 @@
1345+<!DOCTYPE html>
1346+<html>
1347+<head>
1348+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
1349+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
1350+
1351+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
1352+
1353+ <link rel="stylesheet" href="../Style.css">
1354+ <script src="../Script.js" defer></script>
1355+</head>
1356+<body>
1357+ <img src="../Icon.png" width="128" height="128" alt="BAASafariHost Icon">
1358+ <p class="eyebrow">BAA Safari Host</p>
1359+ <h1>请在 Safari 设置中启用 BAA 扩展</h1>
1360+ <p class="lead">
1361+ 这个宿主 App 只负责承载 Safari Web Extension。首版长期 runtime owner 仍然预留给
1362+ <code>controller.html</code>,不是这个宿主窗口。
1363+ </p>
1364+ <p class="state-unknown">你可以在 Safari 设置的“扩展”里启用 BAA Safari Extension。</p>
1365+ <p class="state-on">BAA Safari Extension 当前已启用。接下来请点击 Safari 工具栏里的扩展按钮打开控制页。</p>
1366+ <p class="state-off">BAA Safari Extension 当前未启用。请先在 Safari 设置的“扩展”里打开它。</p>
1367+ <ol class="steps">
1368+ <li>打开 Safari 设置。</li>
1369+ <li>进入“扩展”。</li>
1370+ <li>启用 BAA Safari Extension。</li>
1371+ <li>给 Claude / ChatGPT / Gemini 网站授权。</li>
1372+ </ol>
1373+ <button class="open-preferences">退出并打开 Safari 设置…</button>
1374+</body>
1375+</html>
1376diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Icon.png b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Icon.png
1377new file mode 100644
1378index 0000000000000000000000000000000000000000..423b491de9f0c035d4c9d7736c535bebfe078c8e
1379Binary files /dev/null and b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Icon.png differ
1380diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Script.js b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Script.js
1381new file mode 100644
1382index 0000000000000000000000000000000000000000..48a5ffa86d180d371570d5f63658d71a88ee25c3
1383--- /dev/null
1384+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Script.js
1385@@ -0,0 +1,22 @@
1386+function show(enabled, useSettingsInsteadOfPreferences) {
1387+ if (useSettingsInsteadOfPreferences) {
1388+ document.getElementsByClassName('state-on')[0].innerText = "BAA Safari Extension 当前已启用。接下来请点击 Safari 工具栏里的扩展按钮打开控制页。";
1389+ document.getElementsByClassName('state-off')[0].innerText = "BAA Safari Extension 当前未启用。请先在 Safari 设置的“扩展”里打开它。";
1390+ document.getElementsByClassName('state-unknown')[0].innerText = "你可以在 Safari 设置的“扩展”里启用 BAA Safari Extension。";
1391+ document.getElementsByClassName('open-preferences')[0].innerText = "退出并打开 Safari 设置…";
1392+ }
1393+
1394+ if (typeof enabled === "boolean") {
1395+ document.body.classList.toggle(`state-on`, enabled);
1396+ document.body.classList.toggle(`state-off`, !enabled);
1397+ } else {
1398+ document.body.classList.remove(`state-on`);
1399+ document.body.classList.remove(`state-off`);
1400+ }
1401+}
1402+
1403+function openPreferences() {
1404+ webkit.messageHandlers.controller.postMessage("open-preferences");
1405+}
1406+
1407+document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
1408diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Style.css b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Style.css
1409new file mode 100644
1410index 0000000000000000000000000000000000000000..f7aadd600b769dd3a27f5314f87c890ac57b7ea9
1411--- /dev/null
1412+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/Resources/Style.css
1413@@ -0,0 +1,82 @@
1414+* {
1415+ -webkit-user-select: none;
1416+ -webkit-user-drag: none;
1417+ cursor: default;
1418+}
1419+
1420+:root {
1421+ color-scheme: light dark;
1422+
1423+ --spacing: 20px;
1424+}
1425+
1426+html {
1427+ height: 100%;
1428+}
1429+
1430+body {
1431+ display: flex;
1432+ align-items: center;
1433+ justify-content: center;
1434+ flex-direction: column;
1435+
1436+ gap: var(--spacing);
1437+ margin: 0 calc(var(--spacing) * 2);
1438+ height: 100%;
1439+
1440+ font: -apple-system-short-body;
1441+ text-align: center;
1442+}
1443+
1444+img {
1445+ border-radius: 24px;
1446+}
1447+
1448+.eyebrow {
1449+ margin: 0;
1450+ font-size: 12px;
1451+ font-weight: 700;
1452+ letter-spacing: 0.08em;
1453+ text-transform: uppercase;
1454+ color: #2f688f;
1455+}
1456+
1457+h1 {
1458+ margin: 0;
1459+ max-width: 16em;
1460+ font-size: 28px;
1461+ line-height: 1.25;
1462+}
1463+
1464+.lead,
1465+.steps,
1466+p {
1467+ margin: 0;
1468+ max-width: 34rem;
1469+ line-height: 1.6;
1470+}
1471+
1472+.steps {
1473+ text-align: left;
1474+}
1475+
1476+body:not(.state-on, .state-off) :is(.state-on, .state-off) {
1477+ display: none;
1478+}
1479+
1480+body.state-on :is(.state-off, .state-unknown) {
1481+ display: none;
1482+}
1483+
1484+body.state-off :is(.state-on, .state-unknown) {
1485+ display: none;
1486+}
1487+
1488+button {
1489+ font-size: 1em;
1490+ border-radius: 999px;
1491+ border: 0;
1492+ padding: 11px 16px;
1493+ background: #1f6f5f;
1494+ color: #fff;
1495+}
1496diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHost/ViewController.swift b/plugins/baa-safari/baa-safari-host/BAASafariHost/ViewController.swift
1497new file mode 100644
1498index 0000000000000000000000000000000000000000..42a6de11a74f77d85e090d5320a7f5dc05bfcb70
1499--- /dev/null
1500+++ b/plugins/baa-safari/baa-safari-host/BAASafariHost/ViewController.swift
1501@@ -0,0 +1,57 @@
1502+//
1503+// ViewController.swift
1504+// BAASafariHost
1505+//
1506+// Created by george on 2026/4/2.
1507+//
1508+
1509+import Cocoa
1510+import SafariServices
1511+import WebKit
1512+
1513+let extensionBundleIdentifier = "com.baa.safari.BAASafariHost.Extension"
1514+
1515+class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler {
1516+
1517+ @IBOutlet var webView: WKWebView!
1518+
1519+ override func viewDidLoad() {
1520+ super.viewDidLoad()
1521+
1522+ self.webView.navigationDelegate = self
1523+
1524+ self.webView.configuration.userContentController.add(self, name: "controller")
1525+
1526+ self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
1527+ }
1528+
1529+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
1530+ SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
1531+ guard let state = state, error == nil else {
1532+ // Insert code to inform the user that something went wrong.
1533+ return
1534+ }
1535+
1536+ DispatchQueue.main.async {
1537+ if #available(macOS 13, *) {
1538+ webView.evaluateJavaScript("show(\(state.isEnabled), true)")
1539+ } else {
1540+ webView.evaluateJavaScript("show(\(state.isEnabled), false)")
1541+ }
1542+ }
1543+ }
1544+ }
1545+
1546+ func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
1547+ if (message.body as! String != "open-preferences") {
1548+ return;
1549+ }
1550+
1551+ SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
1552+ DispatchQueue.main.async {
1553+ NSApplication.shared.terminate(nil)
1554+ }
1555+ }
1556+ }
1557+
1558+}
1559diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHostTests/BAASafariHostTests.swift b/plugins/baa-safari/baa-safari-host/BAASafariHostTests/BAASafariHostTests.swift
1560new file mode 100644
1561index 0000000000000000000000000000000000000000..6649c2b53dbb3bf4edb2cda2ffd30d4a259909ce
1562--- /dev/null
1563+++ b/plugins/baa-safari/baa-safari-host/BAASafariHostTests/BAASafariHostTests.swift
1564@@ -0,0 +1,17 @@
1565+//
1566+// BAASafariHostTests.swift
1567+// BAASafariHostTests
1568+//
1569+// Created by george on 2026/4/2.
1570+//
1571+
1572+import Testing
1573+@testable import BAASafariHost
1574+
1575+struct BAASafariHostTests {
1576+
1577+ @Test func example() async throws {
1578+ // Write your test here and use APIs like `#expect(...)` to check expected conditions.
1579+ }
1580+
1581+}
1582diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITests.swift b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITests.swift
1583new file mode 100644
1584index 0000000000000000000000000000000000000000..8b2a1710cbe0c292e8b5293c2facfa13288ee051
1585--- /dev/null
1586+++ b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITests.swift
1587@@ -0,0 +1,43 @@
1588+//
1589+// BAASafariHostUITests.swift
1590+// BAASafariHostUITests
1591+//
1592+// Created by george on 2026/4/2.
1593+//
1594+
1595+import XCTest
1596+
1597+final class BAASafariHostUITests: XCTestCase {
1598+
1599+ override func setUpWithError() throws {
1600+ // Put setup code here. This method is called before the invocation of each test method in the class.
1601+
1602+ // In UI tests it is usually best to stop immediately when a failure occurs.
1603+ continueAfterFailure = false
1604+
1605+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
1606+ }
1607+
1608+ override func tearDownWithError() throws {
1609+ // Put teardown code here. This method is called after the invocation of each test method in the class.
1610+ }
1611+
1612+ @MainActor
1613+ func testExample() throws {
1614+ // UI tests must launch the application that they test.
1615+ let app = XCUIApplication()
1616+ app.launch()
1617+
1618+ // Use XCTAssert and related functions to verify your tests produce the correct results.
1619+ }
1620+
1621+ @MainActor
1622+ func testLaunchPerformance() throws {
1623+ if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
1624+ // This measures how long it takes to launch your application.
1625+ measure(metrics: [XCTApplicationLaunchMetric()]) {
1626+ XCUIApplication().launch()
1627+ }
1628+ }
1629+ }
1630+}
1631diff --git a/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITestsLaunchTests.swift b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITestsLaunchTests.swift
1632new file mode 100644
1633index 0000000000000000000000000000000000000000..a7a8c9bd5e63f7e4c7fd458161e8f764fe046a80
1634--- /dev/null
1635+++ b/plugins/baa-safari/baa-safari-host/BAASafariHostUITests/BAASafariHostUITestsLaunchTests.swift
1636@@ -0,0 +1,33 @@
1637+//
1638+// BAASafariHostUITestsLaunchTests.swift
1639+// BAASafariHostUITests
1640+//
1641+// Created by george on 2026/4/2.
1642+//
1643+
1644+import XCTest
1645+
1646+final class BAASafariHostUITestsLaunchTests: XCTestCase {
1647+
1648+ override class var runsForEachTargetApplicationUIConfiguration: Bool {
1649+ true
1650+ }
1651+
1652+ override func setUpWithError() throws {
1653+ continueAfterFailure = false
1654+ }
1655+
1656+ @MainActor
1657+ func testLaunch() throws {
1658+ let app = XCUIApplication()
1659+ app.launch()
1660+
1661+ // Insert steps here to perform after app launch but before taking a screenshot,
1662+ // such as logging into a test account or navigating somewhere in the app
1663+
1664+ let attachment = XCTAttachment(screenshot: app.screenshot())
1665+ attachment.name = "Launch Screen"
1666+ attachment.lifetime = .keepAlways
1667+ add(attachment)
1668+ }
1669+}
1670diff --git a/plugins/baa-safari/background.js b/plugins/baa-safari/background.js
1671new file mode 100644
1672index 0000000000000000000000000000000000000000..2b90865078f2d679190c4eae5aaac77243d150ca
1673--- /dev/null
1674+++ b/plugins/baa-safari/background.js
1675@@ -0,0 +1,166 @@
1676+const CONTROLLER_URL = browser.runtime.getURL("controller.html");
1677+const STORAGE_KEY = "baaSafari.controllerTabId";
1678+
1679+const runtimeState = {
1680+ background: {
1681+ startedAt: Date.now()
1682+ },
1683+ controller: {
1684+ lastReadyAt: null,
1685+ tabId: null
1686+ },
1687+ lastContentScript: null,
1688+ lastPageInterceptor: null,
1689+ lastUpdatedAt: Date.now(),
1690+ runtimeOwner: "controller.html"
1691+};
1692+
1693+async function getStoredControllerTabId() {
1694+ const data = await browser.storage.local.get(STORAGE_KEY);
1695+ return Number.isInteger(data[STORAGE_KEY]) ? data[STORAGE_KEY] : null;
1696+}
1697+
1698+async function setStoredControllerTabId(tabId) {
1699+ if (Number.isInteger(tabId)) {
1700+ await browser.storage.local.set({ [STORAGE_KEY]: tabId });
1701+ return;
1702+ }
1703+
1704+ await browser.storage.local.remove(STORAGE_KEY);
1705+}
1706+
1707+async function queryControllerTabs() {
1708+ return browser.tabs.query({ url: CONTROLLER_URL });
1709+}
1710+
1711+async function resolveControllerTab() {
1712+ const tabs = await queryControllerTabs();
1713+
1714+ if (tabs.length === 0) {
1715+ await setStoredControllerTabId(null);
1716+ runtimeState.controller.tabId = null;
1717+ runtimeState.lastUpdatedAt = Date.now();
1718+ return null;
1719+ }
1720+
1721+ const storedTabId = await getStoredControllerTabId();
1722+ const canonical =
1723+ tabs.find((tab) => tab.id === storedTabId)
1724+ ?? tabs.find((tab) => Number.isInteger(tab.id))
1725+ ?? null;
1726+
1727+ if (!canonical || !Number.isInteger(canonical.id)) {
1728+ return null;
1729+ }
1730+
1731+ const duplicateIds = tabs
1732+ .map((tab) => tab.id)
1733+ .filter((tabId) => Number.isInteger(tabId) && tabId !== canonical.id);
1734+
1735+ if (duplicateIds.length > 0) {
1736+ await browser.tabs.remove(duplicateIds);
1737+ }
1738+
1739+ runtimeState.controller.tabId = canonical.id;
1740+ runtimeState.lastUpdatedAt = Date.now();
1741+ await setStoredControllerTabId(canonical.id);
1742+ return canonical;
1743+}
1744+
1745+async function ensureControllerTab(options = {}) {
1746+ const activate = options.activate === true;
1747+ let tab = await resolveControllerTab();
1748+
1749+ if (tab == null) {
1750+ tab = await browser.tabs.create({
1751+ active: activate,
1752+ url: CONTROLLER_URL
1753+ });
1754+ await setStoredControllerTabId(tab.id ?? null);
1755+ runtimeState.controller.tabId = Number.isInteger(tab.id) ? tab.id : null;
1756+ } else if (activate) {
1757+ await browser.tabs.update(tab.id, { active: true });
1758+
1759+ if (Number.isInteger(tab.windowId)) {
1760+ await browser.windows.update(tab.windowId, { focused: true });
1761+ }
1762+ }
1763+
1764+ runtimeState.lastUpdatedAt = Date.now();
1765+ return tab;
1766+}
1767+
1768+function updateRuntimeState(patch) {
1769+ Object.assign(runtimeState, patch);
1770+ runtimeState.lastUpdatedAt = Date.now();
1771+}
1772+
1773+browser.runtime.onInstalled.addListener(() => {
1774+ void ensureControllerTab({ activate: true });
1775+});
1776+
1777+browser.action.onClicked.addListener(() => {
1778+ void ensureControllerTab({ activate: true });
1779+});
1780+
1781+browser.tabs.onRemoved.addListener((tabId) => {
1782+ if (runtimeState.controller.tabId !== tabId) {
1783+ return;
1784+ }
1785+
1786+ runtimeState.controller.tabId = null;
1787+ runtimeState.controller.lastReadyAt = null;
1788+ runtimeState.lastUpdatedAt = Date.now();
1789+ void setStoredControllerTabId(null);
1790+});
1791+
1792+browser.runtime.onMessage.addListener((message) => {
1793+ if (!message || typeof message !== "object") {
1794+ return undefined;
1795+ }
1796+
1797+ switch (message.type) {
1798+ case "baa_safari.controller_ready":
1799+ updateRuntimeState({
1800+ controller: {
1801+ lastReadyAt: Date.now(),
1802+ tabId: Number.isInteger(message.tabId) ? message.tabId : runtimeState.controller.tabId
1803+ }
1804+ });
1805+ if (Number.isInteger(message.tabId)) {
1806+ void setStoredControllerTabId(message.tabId);
1807+ }
1808+ return Promise.resolve({
1809+ ok: true,
1810+ runtimeOwner: runtimeState.runtimeOwner
1811+ });
1812+ case "baa_safari.get_state":
1813+ return Promise.resolve({
1814+ ...runtimeState
1815+ });
1816+ case "baa_safari.focus_controller":
1817+ return ensureControllerTab({ activate: true }).then((tab) => ({
1818+ ok: true,
1819+ tabId: tab?.id ?? null
1820+ }));
1821+ case "baa_safari.content_script_seen":
1822+ updateRuntimeState({
1823+ lastContentScript: {
1824+ platform: typeof message.platform === "string" ? message.platform : null,
1825+ seenAt: Date.now(),
1826+ url: typeof message.url === "string" ? message.url : null
1827+ }
1828+ });
1829+ return Promise.resolve({ ok: true });
1830+ case "baa_safari.page_interceptor_seen":
1831+ updateRuntimeState({
1832+ lastPageInterceptor: {
1833+ seenAt: Date.now(),
1834+ url: typeof message.url === "string" ? message.url : null
1835+ }
1836+ });
1837+ return Promise.resolve({ ok: true });
1838+ default:
1839+ return undefined;
1840+ }
1841+});
1842diff --git a/plugins/baa-safari/content-script.js b/plugins/baa-safari/content-script.js
1843new file mode 100644
1844index 0000000000000000000000000000000000000000..19fb382833235c1d7f2d9c6507b144b2a287357c
1845--- /dev/null
1846+++ b/plugins/baa-safari/content-script.js
1847@@ -0,0 +1,44 @@
1848+(function bootstrapBaaSafariContentScript() {
1849+ const host = globalThis.location?.hostname || "";
1850+ const platform =
1851+ host === "claude.ai"
1852+ ? "claude"
1853+ : host === "chatgpt.com" || host === "chat.openai.com"
1854+ ? "chatgpt"
1855+ : host === "gemini.google.com"
1856+ ? "gemini"
1857+ : "unknown";
1858+
1859+ function injectPageInterceptor() {
1860+ const root = document.documentElement;
1861+
1862+ if (!root) {
1863+ return;
1864+ }
1865+
1866+ const script = document.createElement("script");
1867+ script.src = browser.runtime.getURL("page-interceptor.js");
1868+ script.dataset.baaSafari = "page-interceptor";
1869+ script.onload = () => {
1870+ script.remove();
1871+ };
1872+ (document.head || root).appendChild(script);
1873+ }
1874+
1875+ browser.runtime.sendMessage({
1876+ platform,
1877+ type: "baa_safari.content_script_seen",
1878+ url: globalThis.location?.href || null
1879+ }).catch(() => {});
1880+
1881+ document.addEventListener("baa:safari-page-interceptor-ready", (event) => {
1882+ browser.runtime.sendMessage({
1883+ type: "baa_safari.page_interceptor_seen",
1884+ url: event?.detail?.url || globalThis.location?.href || null
1885+ }).catch(() => {});
1886+ }, {
1887+ once: true
1888+ });
1889+
1890+ injectPageInterceptor();
1891+})();
1892diff --git a/plugins/baa-safari/controller.css b/plugins/baa-safari/controller.css
1893new file mode 100644
1894index 0000000000000000000000000000000000000000..60a18009fe48faec0fccc04b40d484cd770e6b66
1895--- /dev/null
1896+++ b/plugins/baa-safari/controller.css
1897@@ -0,0 +1,129 @@
1898+* {
1899+ box-sizing: border-box;
1900+}
1901+
1902+body {
1903+ margin: 0;
1904+ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
1905+ background:
1906+ radial-gradient(circle at top left, rgba(21, 92, 159, 0.18), transparent 34%),
1907+ linear-gradient(180deg, #f4f8fc 0%, #eef4f8 100%);
1908+ color: #173247;
1909+}
1910+
1911+.shell {
1912+ max-width: 980px;
1913+ margin: 0 auto;
1914+ padding: 32px 20px 48px;
1915+}
1916+
1917+.hero,
1918+.actions,
1919+.grid,
1920+.panel {
1921+ margin-bottom: 20px;
1922+}
1923+
1924+.eyebrow {
1925+ margin: 0 0 8px;
1926+ font-size: 12px;
1927+ font-weight: 700;
1928+ letter-spacing: 0.08em;
1929+ text-transform: uppercase;
1930+ color: #245e8a;
1931+}
1932+
1933+h1,
1934+h2,
1935+p {
1936+ margin: 0;
1937+}
1938+
1939+.lead {
1940+ margin-top: 10px;
1941+ max-width: 720px;
1942+ line-height: 1.6;
1943+ color: #36566f;
1944+}
1945+
1946+.actions {
1947+ display: flex;
1948+ gap: 12px;
1949+}
1950+
1951+button {
1952+ border: 0;
1953+ border-radius: 999px;
1954+ padding: 11px 16px;
1955+ font: inherit;
1956+ font-weight: 600;
1957+ color: #fff;
1958+ background: #1f6f5f;
1959+ cursor: pointer;
1960+}
1961+
1962+button:last-child {
1963+ background: #245e8a;
1964+}
1965+
1966+.grid {
1967+ display: grid;
1968+ grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
1969+ gap: 14px;
1970+}
1971+
1972+.card,
1973+.panel {
1974+ border: 1px solid rgba(23, 50, 71, 0.08);
1975+ border-radius: 18px;
1976+ padding: 16px;
1977+ background: rgba(255, 255, 255, 0.84);
1978+ backdrop-filter: blur(12px);
1979+}
1980+
1981+.label {
1982+ font-size: 12px;
1983+ font-weight: 700;
1984+ letter-spacing: 0.06em;
1985+ text-transform: uppercase;
1986+ color: #6a879d;
1987+}
1988+
1989+.value {
1990+ margin-top: 10px;
1991+ font-size: 24px;
1992+ font-weight: 700;
1993+}
1994+
1995+.value.off {
1996+ color: #7d8d99;
1997+}
1998+
1999+.meta {
2000+ margin-top: 10px;
2001+ line-height: 1.5;
2002+ color: #4b6578;
2003+}
2004+
2005+.panel-head {
2006+ margin-bottom: 12px;
2007+}
2008+
2009+.code {
2010+ margin: 0;
2011+ overflow: auto;
2012+ white-space: pre-wrap;
2013+ font: 13px/1.6 "SFMono-Regular", ui-monospace, monospace;
2014+ color: #204157;
2015+}
2016+
2017+.steps {
2018+ margin: 0;
2019+ padding-left: 20px;
2020+ color: #36566f;
2021+ line-height: 1.7;
2022+}
2023+
2024+code {
2025+ font-family: "SFMono-Regular", ui-monospace, monospace;
2026+}
2027diff --git a/plugins/baa-safari/controller.html b/plugins/baa-safari/controller.html
2028new file mode 100644
2029index 0000000000000000000000000000000000000000..663742bf9469d73e8b9e20b51dcd71fed2649abe
2030--- /dev/null
2031+++ b/plugins/baa-safari/controller.html
2032@@ -0,0 +1,77 @@
2033+<!doctype html>
2034+<html lang="zh-CN">
2035+<head>
2036+ <meta charset="utf-8">
2037+ <meta name="viewport" content="width=device-width,initial-scale=1">
2038+ <title>BAA Safari 控制页</title>
2039+ <link rel="stylesheet" href="controller.css">
2040+</head>
2041+<body>
2042+ <main class="shell">
2043+ <section class="hero">
2044+ <p class="eyebrow">BAA Safari 控制页</p>
2045+ <h1>Safari Web Extension 基础壳</h1>
2046+ <p class="lead">
2047+ Phase 1 先固定 runtime 合同:长期状态 owner 预留给 <code>controller.html</code>,
2048+ <code>background.js</code> 只负责 bootstrap、聚合轻量状态和聚焦控制页。
2049+ </p>
2050+ </section>
2051+
2052+ <section class="actions">
2053+ <button id="refresh-btn" type="button">刷新状态</button>
2054+ <button id="focus-btn" type="button">聚焦控制页</button>
2055+ </section>
2056+
2057+ <section class="grid">
2058+ <article class="card">
2059+ <p class="label">Runtime Owner</p>
2060+ <p id="runtime-owner" class="value">controller.html</p>
2061+ <p class="meta">Safari 首版不把 WS / 状态机 owner 默认塞进 background。</p>
2062+ </article>
2063+ <article class="card">
2064+ <p class="label">Background</p>
2065+ <p id="background-status" class="value off">等待同步</p>
2066+ <p id="background-meta" class="meta">只承担 bootstrap / tab orchestration。</p>
2067+ </article>
2068+ <article class="card">
2069+ <p class="label">Content Script</p>
2070+ <p id="content-status" class="value off">暂无页面回报</p>
2071+ <p id="content-meta" class="meta">等待 AI 页面注入占位脚本。</p>
2072+ </article>
2073+ <article class="card">
2074+ <p class="label">Page Interceptor</p>
2075+ <p id="interceptor-status" class="value off">未注入</p>
2076+ <p id="interceptor-meta" class="meta">当前只保留页面侧占位钩子。</p>
2077+ </article>
2078+ </section>
2079+
2080+ <section class="panel">
2081+ <div class="panel-head">
2082+ <h2>职责边界</h2>
2083+ </div>
2084+ <pre id="contract-view" class="code"></pre>
2085+ </section>
2086+
2087+ <section class="panel">
2088+ <div class="panel-head">
2089+ <h2>最近状态</h2>
2090+ </div>
2091+ <pre id="state-view" class="code"></pre>
2092+ </section>
2093+
2094+ <section class="panel">
2095+ <div class="panel-head">
2096+ <h2>本地启用步骤</h2>
2097+ </div>
2098+ <ol class="steps">
2099+ <li>先构建并启动 <code>BAASafariHost</code> 宿主 App。</li>
2100+ <li>在 Safari 设置里启用 BAA 扩展。</li>
2101+ <li>给 Claude / ChatGPT / Gemini 站点授权。</li>
2102+ <li>点击扩展按钮,让 <code>background.js</code> 打开本控制页。</li>
2103+ </ol>
2104+ </section>
2105+ </main>
2106+
2107+ <script src="controller.js"></script>
2108+</body>
2109+</html>
2110diff --git a/plugins/baa-safari/controller.js b/plugins/baa-safari/controller.js
2111new file mode 100644
2112index 0000000000000000000000000000000000000000..bcf0ab3fd4323d16e2ef0aaf1574e65ec99b3c4c
2113--- /dev/null
2114+++ b/plugins/baa-safari/controller.js
2115@@ -0,0 +1,129 @@
2116+const REFRESH_INTERVAL_MS = 2_000;
2117+
2118+const elements = {
2119+ backgroundMeta: document.getElementById("background-meta"),
2120+ backgroundStatus: document.getElementById("background-status"),
2121+ contentMeta: document.getElementById("content-meta"),
2122+ contentStatus: document.getElementById("content-status"),
2123+ contractView: document.getElementById("contract-view"),
2124+ focusButton: document.getElementById("focus-btn"),
2125+ interceptorMeta: document.getElementById("interceptor-meta"),
2126+ interceptorStatus: document.getElementById("interceptor-status"),
2127+ refreshButton: document.getElementById("refresh-btn"),
2128+ runtimeOwner: document.getElementById("runtime-owner"),
2129+ stateView: document.getElementById("state-view")
2130+};
2131+
2132+function formatTimestamp(value) {
2133+ return Number.isFinite(value) ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "未记录";
2134+}
2135+
2136+function stringify(value) {
2137+ return JSON.stringify(value, null, 2);
2138+}
2139+
2140+function setValue(node, text, active = true) {
2141+ if (!node) {
2142+ return;
2143+ }
2144+
2145+ node.textContent = text;
2146+ node.classList.toggle("off", !active);
2147+}
2148+
2149+function buildContractSnapshot() {
2150+ return {
2151+ background_js: "bootstrap / focus controller / aggregate placeholder state",
2152+ content_script_js: "page entrypoint and injector placeholder",
2153+ controller_html: "intended long-lived runtime owner for WS and state machine",
2154+ controller_js: "render runtime contract and bootstrap shell diagnostics",
2155+ page_interceptor_js: "page-world placeholder only; no final-message or proxy_delivery yet",
2156+ runtime_owner: "controller.html"
2157+ };
2158+}
2159+
2160+async function readCurrentTabId() {
2161+ if (!browser?.tabs?.getCurrent) {
2162+ return null;
2163+ }
2164+
2165+ try {
2166+ const tab = await browser.tabs.getCurrent();
2167+ return Number.isInteger(tab?.id) ? tab.id : null;
2168+ } catch {
2169+ return null;
2170+ }
2171+}
2172+
2173+async function fetchState() {
2174+ return browser.runtime.sendMessage({
2175+ type: "baa_safari.get_state"
2176+ });
2177+}
2178+
2179+function renderState(state) {
2180+ elements.runtimeOwner.textContent = state.runtimeOwner;
2181+ setValue(
2182+ elements.backgroundStatus,
2183+ state.background.startedAt == null ? "未启动" : "已启动",
2184+ state.background.startedAt != null
2185+ );
2186+ elements.backgroundMeta.textContent =
2187+ `最近同步: ${formatTimestamp(state.lastUpdatedAt)}\ncontrollerTabId: ${state.controller.tabId ?? "无"}`;
2188+
2189+ const lastContent = state.lastContentScript ?? null;
2190+ setValue(
2191+ elements.contentStatus,
2192+ lastContent == null ? "暂无页面回报" : (lastContent.platform || "unknown"),
2193+ lastContent != null
2194+ );
2195+ elements.contentMeta.textContent =
2196+ lastContent == null
2197+ ? "等待 Claude / ChatGPT / Gemini 页面触发 content-script。"
2198+ : `${lastContent.url}\n最近触达: ${formatTimestamp(lastContent.seenAt)}`;
2199+
2200+ const lastInterceptor = state.lastPageInterceptor ?? null;
2201+ setValue(
2202+ elements.interceptorStatus,
2203+ lastInterceptor == null ? "未注入" : "已注入",
2204+ lastInterceptor != null
2205+ );
2206+ elements.interceptorMeta.textContent =
2207+ lastInterceptor == null
2208+ ? "还没有页面 world 侧占位事件。"
2209+ : `${lastInterceptor.url}\n最近触达: ${formatTimestamp(lastInterceptor.seenAt)}`;
2210+
2211+ elements.contractView.textContent = stringify(buildContractSnapshot());
2212+ elements.stateView.textContent = stringify(state);
2213+}
2214+
2215+async function refreshState() {
2216+ const state = await fetchState();
2217+ renderState(state);
2218+}
2219+
2220+async function boot() {
2221+ const tabId = await readCurrentTabId();
2222+
2223+ await browser.runtime.sendMessage({
2224+ tabId,
2225+ type: "baa_safari.controller_ready"
2226+ });
2227+
2228+ await refreshState();
2229+
2230+ elements.refreshButton?.addEventListener("click", () => {
2231+ void refreshState();
2232+ });
2233+ elements.focusButton?.addEventListener("click", () => {
2234+ void browser.runtime.sendMessage({
2235+ type: "baa_safari.focus_controller"
2236+ });
2237+ });
2238+
2239+ globalThis.setInterval(() => {
2240+ void refreshState();
2241+ }, REFRESH_INTERVAL_MS);
2242+}
2243+
2244+void boot();
2245diff --git a/plugins/baa-safari/icons/128.png b/plugins/baa-safari/icons/128.png
2246new file mode 100644
2247index 0000000000000000000000000000000000000000..423b491de9f0c035d4c9d7736c535bebfe078c8e
2248Binary files /dev/null and b/plugins/baa-safari/icons/128.png differ
2249diff --git a/plugins/baa-safari/icons/48.png b/plugins/baa-safari/icons/48.png
2250new file mode 100644
2251index 0000000000000000000000000000000000000000..e5bb1f5ad3a8b1842c8a6020d85f3aab85586956
2252Binary files /dev/null and b/plugins/baa-safari/icons/48.png differ
2253diff --git a/plugins/baa-safari/icons/96.png b/plugins/baa-safari/icons/96.png
2254new file mode 100644
2255index 0000000000000000000000000000000000000000..604f9c2730425810c11ca374123c4819bc82b9a4
2256Binary files /dev/null and b/plugins/baa-safari/icons/96.png differ
2257diff --git a/plugins/baa-safari/manifest.json b/plugins/baa-safari/manifest.json
2258new file mode 100644
2259index 0000000000000000000000000000000000000000..1a1f7dbe5930ee80aeea1409bd10e88ecb06257d
2260--- /dev/null
2261+++ b/plugins/baa-safari/manifest.json
2262@@ -0,0 +1,47 @@
2263+{
2264+ "manifest_version": 2,
2265+ "name": "BAA Safari",
2266+ "version": "0.1.0",
2267+ "description": "Minimal Safari Web Extension shell for BAA conductor integration.",
2268+ "icons": {
2269+ "48": "icons/48.png",
2270+ "96": "icons/96.png",
2271+ "128": "icons/128.png"
2272+ },
2273+ "permissions": [
2274+ "storage",
2275+ "tabs",
2276+ "webNavigation",
2277+ "webRequest",
2278+ "https://claude.ai/*",
2279+ "https://chatgpt.com/*",
2280+ "https://chat.openai.com/*",
2281+ "https://gemini.google.com/*"
2282+ ],
2283+ "background": {
2284+ "scripts": [
2285+ "background.js"
2286+ ],
2287+ "persistent": true
2288+ },
2289+ "browser_action": {
2290+ "default_title": "BAA Safari"
2291+ },
2292+ "content_scripts": [
2293+ {
2294+ "matches": [
2295+ "https://claude.ai/*",
2296+ "https://chatgpt.com/*",
2297+ "https://chat.openai.com/*",
2298+ "https://gemini.google.com/*"
2299+ ],
2300+ "js": [
2301+ "content-script.js"
2302+ ],
2303+ "run_at": "document_start"
2304+ }
2305+ ],
2306+ "web_accessible_resources": [
2307+ "page-interceptor.js"
2308+ ]
2309+}
2310diff --git a/plugins/baa-safari/page-interceptor.js b/plugins/baa-safari/page-interceptor.js
2311new file mode 100644
2312index 0000000000000000000000000000000000000000..45a8c64beda0bfc3fa3347d0deb56ebd5c7e87cf
2313--- /dev/null
2314+++ b/plugins/baa-safari/page-interceptor.js
2315@@ -0,0 +1,18 @@
2316+(function bootstrapBaaSafariPageInterceptor() {
2317+ if (globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__?.ready === true) {
2318+ return;
2319+ }
2320+
2321+ globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__ = {
2322+ ready: true,
2323+ installedAt: Date.now(),
2324+ url: globalThis.location?.href || null
2325+ };
2326+
2327+ document.dispatchEvent(new CustomEvent("baa:safari-page-interceptor-ready", {
2328+ detail: {
2329+ installedAt: globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__.installedAt,
2330+ url: globalThis.__BAA_SAFARI_PAGE_INTERCEPTOR__.url
2331+ }
2332+ }));
2333+})();