Search:一个 3 MB 的 macOS 浏览器,把 Chrome 那套东西全删了,还跑起了 Chrome 扩展

Search:一个 3 MB 的 macOS 浏览器,把 Chrome 那套东西全删了

Mac 上的浏览器这些年越来越不像工具,越来越像一个产品:工具栏、起始页、推荐侧栏、账号登录、一堆要你点确认的弹窗。有个设计工作室干脆自己写了一个——Search,一个 3 MB、用系统自带 WebKit 的 macOS 浏览器,GitHub 上四天冲到 632 星,MIT 开源。

它长什么样

没有工具栏。打开就是一排标签页(默认横在顶上,也可以竖在左边)+ 一张网页,中间什么都没有。

你想输入地址就输地址,想搜索就输几个词,同一个输入框。地址补全是拿你自己的历史记录补的,你按回车之前,输入的内容不会发给任何地方

窗口只有一个,「新建」只有新标签页这一种。没有账号、没有同步、没有云、没有遥测,官方 README 里写得很直白:离开你这台 Mac 的东西,只有你主动请求的网页、它们的图标,以及每天一次检查新版本的请求。

安装包大概 3 MB,因为它用的引擎是每台 Mac 里本来就有的 WebKit——也就是 Safari 跑的那个,不需要再下一份 Chromium 常驻内存。

安装方式:

brew install --cask driceroland/tap/search

要求 macOS 14 或更高。官网在 officecommun.com/search

哪些功能是「自带」的

Search 的作者说,他们一天到晚都在浏览器里,实在受够了那些「产品化」的浏览器,于是把常用能力全部做进了本体,而不是让你装插件才有:

能力 怎么用
阅读模式 ⇧⌘R 把页面剥成文章
永久隐藏元素 ⇧⌘H 之后点掉 cookie 横幅、订阅弹层、相关推荐
广告拦截 网络层拦截,页面还没开始渲染就已经拦掉了
视频浮窗 ⇧⌘P 把视频抠出来贴在所有窗口上面
密码 存在 macOS 钥匙串里,登录真的成功了才问你要不要保存
导入数据 一键从 Chrome、Arc、Dia、Brave、Edge 导进来,全程不出本机

其中「永久隐藏」这个设计挺有意思:隐藏规则是按站点存的一组 CSS 选择器,在文档开始渲染时注入。所以下次进这个站,这个元素在一帧都还没画出来的时候就已经没了,不会出现「先闪一下再消失」。

广告拦截也是同理,它是启动时编译好的 WKContentRuleList,在 WebKit 的网络层生效,请求根本没发出去;而 JavaScript 拦截器永远做不到这一点,还要在运行时付出代价。

隐私不是口号,是一张表

README 里有一张很诚实的表,写清楚了每样东西存在哪、谁能读:

  • 密码 —— macOS 登录钥匙串里,标记为 Search 的普通钥匙串条目。别的 App 想读会弹系统授权框。
  • 历史、书签、打开的标签、被隐藏的元素 —— ~/Library/Application Support/Search/ 下的小 JSON 文件。只有你能读。
  • Cookie 和站点数据 —— WebKit 自己给这个 App 的存储空间,和其他浏览器一样。
  • 扩展 —— 解包在 ~/Library/Application Support/Search/Extensions/,数据在 WebKit 的扩展存储里。
  • 其他一切 —— 不存在。它没有服务器。

另外 ⇧⌘N 开的隐私标签页有自己的 Cookie 罐,关掉什么都不留。

最狠的一块:把 Chrome 扩展跑在 WebKit 上

这是整个项目技术含量最高的地方,也是我决定写它的原因。

Search 支持装 Chrome 扩展,但它里面没有 Chrome。它跑的是 WebKit 自己的扩展引擎——也就是 Safari 用的那个。问题是 Chrome 和 WebKit 的扩展 API 并不一样,Chrome 有而 WebKit 没有的那些(书签、历史、下载、侧边栏、offscreen documents、字体、通知、语音、OAuth 登录)Search 自己实现了。

具体做法分几层(README 的 “How it’s put together” 一节):

  • 装扩展时ExtensionShims.swift 会给扩展的 worker、页面和 content script 注入一小段脚本,把 WebKit 缺的 Chrome API 定义成「由 Search 原生回答的调用」——包括 userScriptsprivacybrowsingDatasessions,以及老的 FileSystem API 等等。
  • 它还要修补 WebKit 和 Chrome 行为不一致的地方:页面不回答的回复、worker 启动之后才加的监听器、WebKit 跟丢的 worker、它漏掉的成员和常量。
  • 扩展页面从 chrome-extension:/// 提供,也就是它在 Chrome 里的地址,这样服务器和网站能认出它。
  • Crx.swift 去 Chrome 网上应用店的公开更新地址取扩展,解包前先验 CRX3 签名,跟扩展 ID 对上才装。
  • Chrome 的 native messaging 也实现了,对的是 Chrome 注册的 NativeMessagingHosts 目录里的宿主。

它甚至有个 ./bench ext-* 命令,可以从 shell 里驱动整套扩展流程做测试。

用脚本在「正在用的浏览器」里跑测试

这块也很少见。设置里打开 Settings › General › Let a script drive Search,正在运行的 App 会在自己的目录下监听一个 Unix socket(只有你自己这个用户能读),然后仓库根目录的 ./bench 就能跟它说话:

./bench open https://example.com     # 开一个自己独占的标签页,行尾带个烧瓶标记
./bench wait 2e7e7e89                # 等它加载完
./bench text 2e7e7e89                # 取页面文字
./bench shot 2e7e7e89 out.png        # 截图
./bench click 2e7e7e89 "button.go"   # 点击、输入、提交,走页面自己的事件
./bench probe                        # 窗口状态:开了哪些面板、有没有模态框、所有窗口
./bench close all

关键是 bench 标签页永远不会被切到前台、不进会话、不进历史,脚本说关就关。也就是说,你可以一边正常用浏览器,一边跑自动化测试,两边互不干扰。对于想做浏览器端到端测试的人来说,这个设计值得抄。

代码只有 12700 行,一个文件管一件事

作者把源码放出来,理由写得很实在:「让任何人都能看清楚一个拿着你密码和历史的浏览器到底在干什么」。

  • 12,700 行 Swift 左右,除了 macOS 自带的东西之外没有任何依赖,一个文件管一件事。
  • 界面用 SwiftUI,少数 SwiftUI 够不到的地方用 AppKit(窗口标题栏、拖空白的标签栏拖动窗口),页面用 WKWebView
  • 每个页面一个 Tabweb view 是懒建的——从上次会话恢复的标签不切换过去就不占进程。这就是为什么它有二十个标签启动还是秒开。
  • 文件命名很直白:Vault.swift 是钥匙串,Shield.swift 是广告拦截,Curtain.swift 是隐藏元素,Session.swift 是启动恢复,Updater.swift 是更新,Bench.swift 是测试 socket。不用先学一套自研框架

自己构建:

swift build        # 直接从 SwiftPM 跑
./build.sh         # 组装出能双击的 Search.app,ad-hoc 签名
./build.sh release dmg

要求 macOS 14+、Xcode 16 / Swift 6 工具链。自己构建的版本没做公证,第一次打开要右键 → 打开。

值不值得看

值得。理由不是「又一个浏览器」,而是它展示了三件在大项目里很少能看清的事:

1. 怎么用系统自带的引擎做出一个 3 MB、秒开的真产品,而不是再包一层 Chromium。

2. 怎么在另一套扩展引擎上兼容 Chrome 扩展——签名校验、API 垫片、行为差异修补、chrome-extension:// 地址,一整套都摊开给你看。

3. 怎么在不打断用户的前提下对正在运行的 GUI App 做端到端测试

如果你在写 macOS App、写浏览器扩展,或者只是嫌现在的浏览器太重,driceroland/Search 都值得翻一翻——一万两千行,一个周末能读完的那种。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容