
单输入框双模式这款 3MB 浏览器的地址栏设计哲学拆解【免费下载链接】SearchA small, fast WebKit browser for macOS, by Office Commun.项目地址: https://gitcode.com/gh_mirrors/search59/Search浏览器地址栏在过去二十年里经历了合一—堆料—再回归的三段式演化。如今主流浏览器的输入框上方往往叠加着站点 Logo、AI 摘要、新闻流、起始页、账户头像——输入框本身只是入口之一。而 SearchOffice Commun 出品、基于 WebKit 的 macOS 浏览器磁盘占用约 3MB给出的答案是整个浏览器只有一个输入框且它同时且仅同时回答两个问题——你要去哪和你想搜什么。这不是功能缺失而是一套被代码严格约束的设计哲学。本文从仓库源码出发拆解这个单输入框背后的双模式判定链、键盘流设计以及砍掉 UI 的取舍清单。一、单输入框的交互史从 OmniBox 到极简主义的回归2006 年 Firefox 2 引入智能地址栏Smart Location Bar开始把历史记录补全塞进地址栏2008 年 Chrome 用 OmniBox 把输入地址和输入搜索词合并进同一个字段成为此后所有浏览器的默认形态。但合一之后是不断的堆料Chrome 后续把应用启动器、账户、站点建议塞进同一区域Arc 等新浏览器则把地址栏升级为命令栏让输入框承担站点搜索、快捷指令甚至 AI 提问。Search 的回归不是回到 Firefox 1 的地址栏和搜索框分列两端而是把合一的输入框重新做成唯一的输入框。仓库 README.md 的描述很直白One field.Type an address and you go there; type words and you search. It finishes addresses from your own history and never sends what you type anywhere until you press Return.与之配套的是没有工具栏、没有起始页、没有侧边栏建议、没有账号。整个窗口只有一行标签页和页面本身。在 Omnibox.swift 的头部注释里这个设计被写得近乎执拗One field, in the middle, and the few places it thinks you mean. It takes addresses and only addresses: type something that isnt a place and it shivers and says so, rather than quietly handing your keystrokes to a search engine.注意后半句——它只接受地址。这与 Chrome 的哲学截然相反Chrome 会把你打的任何内容模糊处理成搜索请求而 Search 要求输入内容必须能判定为一个地址否则它宁可颤抖着拒绝也不肯悄悄把你的按键交给搜索引擎。二、双模式判定逻辑一条 5 步决策链单输入框最核心的工程问题只有一个按下 Return 时怎么知道你打的是地址还是搜索词大多数浏览器用看起来像域名就当作地址否则就搜索的启发式。Search 的答案在 Browser.swift 的destination(for:)中是一段严格有序的决策链func destination(for typed: String) - URL? { if let site siteChip { return site.url(for: typed) } // 1. 字段里已有站点芯片 → 站内搜索 if let url Address.url(from: typed) { return url } // 2. 输入本身是合法地址 → 直接前往 if let (keyword, rest) Keyword.match(typed, in: prefs.keywords), let url Engine.url(for: rest, template: keyword.template) { // 3. 命中关键字快捷词yt cats→ 定向搜索 return url } return searchURL(for: typed) // 4. 兜底交给默认搜索引擎 }判定核心Address.url 的严格语法第二步的Address.url(from:)Address.swift是整条链的守门员它的严格程度决定了双模式的边界含空格的输入直接判为不是地址——hello world 不可能是一个网站scheme 白名单只有http/https/file/about/datamailto:、自定义协议一律拒绝主机名必须通过looksLikeHost校验至少两个点分标签、TLD 全部为字母todo 不是域名因为它只有一个标签以数字结尾的1.2.3.4.5是版本号也不是地址、四个数字段视为内网 IP、IPv6 的[::1]单独处理协议选择按目标推断localhost、.local域名、192.168.*、10.*、Docker 常用的172.16–31.*走http://其余一律https://——因为本地服务器几乎没有证书强行 https 只会得到连接失败。建议列表搜索永远排在地址之后同一个判定还被用于构建输入框下方的建议列表guess()Browser.swiftvar list history.suggestions(for: typed, limit: 3) // 最多 3 条来自你自己历史记录的建议 if !typed.isEmpty, Address.url(from: typed) nil { // 只有当它不可能是个地址时 list.append(Suggestion(key: typed, …, kind: .search)) // 才会追加一条搜索建议 } if let command { list.insert(.command(command), at: 0) } // 命令栏词条置顶注意两个细节历史建议上限是 3 条列表长到阅读成本超过输入地址本身搜索建议只在输入被判定为不可能是个地址时才出现而且排在列表末尾。灰色补全ending同样来自你的历史记录——Search 不会把输入发送给任何远程补全服务直到你按下 Return。这就是 README 里finishes addresses from your own history的源码落点。提交优先级与拒绝反馈submit()的优先级链同样严格箭头键选中的行 灰色补全的地址 你实际打的内容。如果三者都无法构成 URL输入框会拒绝而不是静默转搜索——refusals 1触发 Omnibox 的抖动动画Omnibox.swift 中Shake修饰符边框短暂变红明说这不是一个地方。三、键盘流设计一个字段整条键盘单输入框要成立必须把键盘操作做得比任何可视化控件都快。Search 的字段交互基本可以用一句话概括全程键盘无鼠标依赖。按键行为⌘L唤起输入框当前地址整段选中直接输入即替换Tab接受灰色补全的剩余部分↑/↓在建议列表中行走走出列表顶端则放下列表Return按优先级链提交Esc收回输入框先清掉站点芯片再回到页面为什么用 NSTextField 而不是 SwiftUI TextFieldOmnibox.swift 中AddressField的注释点出了一个只有实现者才懂的细节SwiftUI 的 TextField 只能持有你打的那段字符串而这里必须持有你没打的那部分——灰色补全的地址后缀要真实存在于字段里且处于选中状态这样继续输入会替换它、Return 会接受它。这需要一个真正的NSTextField和它的 delegate。Coordinator里甚至专门处理了删除键必须真的删得掉的问题如果不加保护灰色补全会在每次退格后被原样写回形成一个永远缩短不了的输入框。从地址模式切换到搜索模式站点芯片这是双模式设计里最优雅的一环源码注释写得很清楚在地址栏搜索一个站点如 Arc设置 › 通用SiteSearch.swift。你输入red列表顶部会出现Search Reddit按Tab后站点以一个小芯片SiteChip的形式进入输入框func lockSiteOffer() - Bool { guard prefs.searchesSites, siteChip nil, let site siteOffer else { return false } siteOffer nil typed siteChip site // 之后输入的任何词都进入该站点的搜索 return true }此后destination(for:)的第一分支接管输入框内的所有词都通过该站点的模板构造站内搜索 URL。内置了 15 个站点Reddit、YouTube、X、ChatGPT、Claude、Perplexity、Wikipedia、GitHub、Stack Overflow、MDN、Amazon、Google Maps、IMDb、Spotify、Figma且你去过的、声明了 OpenSearch 的站点会自动加入。字段里已经有芯片时按⌫可以把它拿走。这个特性默认关闭Prefs.swift 中searchesSites注释Off unless asked for——符合项目功能默认最小化的一贯原则。第三种模式⌘K 切换器与命令词严格说这不止双模式。summoning状态让同一个字段变成标签切换器⌘K时列表只回答我已经打开了哪些页guess()直接短路到openPages(matching:)picked预设为最新页面——⌘K 然后 Return 就是整个手势。另有可选的命令栏AddressCommands.swift输入settings、history、downloads、passwords等词回车直接打开应用内对应面板而不是去 Google 搜这个词。同样默认关闭注释解释得很实在没人指望在浏览器里输入 settings 会打开应用自己的设置面板。四、砍掉 UI 的背后为隐私与速度让路的取舍清单单输入框不是孤立的 UI 决定它依附于一整份不做什么清单。README 的 What it doesnt do 部分几乎和 What it does 一样长速度的取舍3MB 的根源是复用系统已有的 WebKit——没有第二份 Chromium 要下载、更新、常驻内存这也是启动和新建标签近乎即时的原因约 42,000 行 Swift零第三方依赖一个文件一个关注点Vault.swift是钥匙串、Shield.swift是广告拦截、Session.swift是会话恢复广告拦截是编译一次的WKContentRuleList在 WebKit 网络层拦截运行时零开销而非 JS 注入式拦截会话恢复的 Tab 是懒构建的——带着二十个标签启动依然即时因为不点开就不占进程。这个优化在代码里甚至细到动画层面Breath输入框下方的呼吸微光从 SwiftUI 动画改为 Core Animation 图层后空标签页的 CPU 占用从18% 的一个核心降到了渲染服务器托管Omnibox.swift 中的实测注释。隐私的取舍没有同步、没有账号、没有云。README 的原文是离开你 Mac 的只有你请求的页面、它们的图标、以及每天一次检查是否有新版本的小请求没有遥测、没有分析、不上报崩溃密码只进 macOS 系统钥匙串历史书签是本地 JSON 文件唯一窗口、无多窗口模式——标签页是唯一的新开方式。结语Search 的地址栏证明了单输入框并不等于功能少而是一种更严格的输入契约它能接受的东西被精确界定不能接受的会明确拒绝。双模式之所以没有退化成模糊匹配是因为判定逻辑Address.url的语法门禁先于建议列表搜索建议只在不可能是个地址时才出现先于提交动作拒绝时抖动提示形成了一条完整的链路键盘流则把这条链路的每一步都映射成一个键。在浏览器产品普遍把输入框做成增长入口的今天这种把 UI 砍到只剩一个字段、把逻辑做进判定链的做法反而显出一种工程上的克制与自信——工具应该安静这是它最响亮的主张。【免费下载链接】SearchA small, fast WebKit browser for macOS, by Office Commun.项目地址: https://gitcode.com/gh_mirrors/search59/Search创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考