宿主接口与Capability
当前SDK0.1.0,hostApiMajor/apiMajor=1、protocolVersion=1。类型声明与机器可读字段以sdk唯一来源同步。
组件自行通过直接联网或通用network接口完成登录、令牌和服务请求;参见组件登录验证。
参数与关联协议
HTTP头与目标参数规则:HTTP 头必须在明文 {name,value} 与秘密 {name,secretBindingId,prefix?} 中二选一,明文形式不得携带 prefix。target.open 也必须在 {bindingId} 与 {directoryBindingId,relativePath} 中二选一,不接受混合字段或固定绑定附加 relativePath;捕获票据和执行复用同一选择器检查,非法形式返回 INVALID_ARGUMENT。此处拒绝歧义输入,不扩张已有目标范围或权限。
可回收实例二进制缓存的额度、未命中、取消与授权失效见缓存协议,与存档分离,不提供本机路径或新增系统读取权限。
持久提醒的接口、有效期、取消与运行期投递以持久提醒为权威定义;沿用可拒绝的 notifications 权限。
正常退出前的有界 onClosing 保存机会;事件、回应及限制统一见实例存档,不增加 Capability 或作者退出否决权。
音频上下文与用户策略见宿主音频控制,context.audio 和 audio.changed;作者不能写宿主音频策略。实际支持与检查范围见实现说明。
原生接口边界
画面采集、桌面空间、丰富剪贴板、媒体补全、系统输出音量、文件图像和用户空闲状态见系统集成。各项只提供已实现的系统能力,作者自行实现业务逻辑。
组件控制优先复用WebView2已有实现:实例/全局静音均落到每个WebView的IsMuted,暂停使用TrySuspend/Resume;定位由WebView的PermissionRequested事件按Services授权决定,且不向profile保存可绕过宿主的授权。窗口输入、显隐与导航继续使用对应Controller和Composition接口。
WebView2宿主接口没有等价的受限目录、已绑定目标打开、系统媒体会话或系统指标接口;这些接口沿用Windows原生API代理。浏览器异步Clipboard API由原生Permissions-Policy禁用读写,避免脚本绕过宿主授权;宿主剪贴板桥接调用Windows剪贴板API,文件选择和导出使用系统对话框及受限句柄;不能用网页API替换后丢失绑定范围、额度、取消或最终投递检查。组件直接fetch/WebSocket/WebRTC仍由浏览器执行;带宿主秘密绑定的network接口仍由原生HTTP代理执行。音量控制和系统指标复用统一原生代理,不在界面或组件中另建授权入口。
宿主主题
功能标志 context.theme:握手上下文的 theme 返回宿主管理界面实际使用的 light(昼/浅色)或 dark(夜/深色),无需 Capability。宿主从管理界面根元素的 ActualTheme 发布实际值,包含设置覆盖及跟随系统的结果;它与 system.theme 的 Windows 系统偏好分别定义。组件只读,不能修改宿主主题,也不会被宿主强制改写页面样式。
通过 (await widgetHost.ready).theme 读取初始值,通过 widgetHost.context.theme 读取当前值。实际值改变发送 theme.changed,数据为 {theme};SDK 先更新 context 再通知监听器,相同值去重。隐藏组件继续更新;全局暂停期间只保留最新值,恢复后发送实际变化,不积压中间事件或唤醒网页。沿用文档会话边界,导航/关闭后的旧消息无效。
await widgetHost.ready;
const renderTheme = theme => { document.documentElement.dataset.theme = theme; };
renderTheme(widgetHost.context.theme);
const offTheme = widgetHost.on('theme.changed', ({theme}) => renderTheme(theme));
window.addEventListener('pagehide', offTheme, {once: true});强调色与文字缩放
context.system.accentColor 为宿主使用的 Windows 强调色,固定大写 #RRGGBB,不可用时为 null。context.system.textScaleFactor 为系统辅助功能文字大小倍率,范围 1–2.25,不可用时为 null。它与 environment.scale 的显示器 DPI 比例分别定义,不返回精确显示器或用户身份。功能标志分别为 context.system.accentColor 和 context.system.textScaleFactor,无需 Capability。
初始值随 ready.system 返回,未完成采样时允许 null;首次采样及后续实际变化通过既有 system.changed 整体投递,SDK 在通知前更新 context.system。沿用有限系统上下文的有界后台采样与隐藏/暂停规则;暂停期间不采样,恢复后读取最新状态,不积压中间变化。组件自行应用配色和文字大小,宿主不修改网页 CSS、浏览器缩放或窗口尺寸;高对比时优先遵循强制色规则,不把强调色视为对比度保证。
await widgetHost.ready;
const applySystemAppearance = ({accentColor, textScaleFactor}) => {
const style = document.documentElement.style;
if (accentColor) style.setProperty('--host-accent', accentColor);
else style.removeProperty('--host-accent');
style.setProperty('--host-text-scale', String(textScaleFactor ?? 1));
};
applySystemAppearance(widgetHost.context.system);
const offSystem = widgetHost.on('system.changed', applySystemAppearance);
window.addEventListener('pagehide', offSystem, {once: true});组件右键菜单
无权限方法 contextMenu.setItems({items}) 整组替换当前实例文档的菜单注册,成功返回 {}。items 最多 16 项,空数组清空;每项为 {id,label,enabled?,checked?},id 为 1–64 个 ASCII 字母、数字、下划线、连字符或句点,整组唯一;label 为 1–80 个 UTF-16 单元的非空白纯文本,不接受控制字符、无效代理项、换行或制表符,不解释 & 助记键。enabled 默认 true;省略 checked 为普通动作,提供布尔 checked 为勾选动作。未知字段、错误类型、重复 ID、无效文本返回 INVALID_ARGUMENT;超项数返回 QUOTA_EXCEEDED。校验失败保留上次注册。
用户在组件可交互区域请求原生右键菜单时,仅展示组件注册的业务动作,移除 WebView2 浏览器默认菜单项;没有有效业务动作时不显示菜单。页面自行阻止默认 contextmenu 时沿用网页行为;纯展示穿透区及编辑遮罩不增加右键捕获。仅页面URI和框架URI均匹配当前宿主入口及文档会话时展示,沿用既有子框架导航拒绝;隐藏、全局暂停、锁定/系统挂起、编辑或关闭期间不展示组件动作。选中已启用项后发送 contextMenu.selected,数据仅为 {id};勾选不会自动更改作者状态,作者完成业务后重新注册。标签由作者按当前语言生成,不读取选择文本、链接、剪贴板或磁盘路径,不提供系统命令、URL 或程序执行入口。该事件不是网页物理输入证明,后续能力仍按原有权限及执行边界检查。
菜单注册仅在当前文档内存中保留,整组替换、页面导航、停用、卸载、关闭和版本切换使旧菜单选择失效,不进入配置、布局、KV或存档。每次请求只保留一组有界原生菜单项及监听;打开和最终投递均核对 Services 实例/安装失效代次及当前文档、注册代次和显示状态。多显示器各实例独立注册,无跨实例派发。
await widgetHost.ready;
const offMenu = widgetHost.on('contextMenu.selected', ({id}) => {
if (id === 'refresh') refreshWeather();
});
await widgetHost.call('contextMenu.setItems', {items: [
{id: 'refresh', label: '立即刷新'},
{id: 'celsius', label: '摄氏度', checked: true}
]});
window.addEventListener('pagehide', offMenu, {once: true});宿主语言
功能标志i18n提供包内翻译:握手上下文的i18n为已校验字典(未声明则为空对象),SDK的widgetHost.i18n.t(key)按当前宿主语言及默认语言解析,widgetHost.i18n.language返回实际语言。data-i18n纯文本及显式属性绑定自动更新,每文档最多处理2048个绑定元素,缺失键诊断最多64个;绑定及字典格式见包内多语言。无需Capability或作者读取context.language,仍沿用以下语言事件与生命周期边界。
功能标志 context.language:握手上下文的 language 返回宿主管理界面实际使用的 zh-CN 或 en-US,不需要 Capability,全部权限拒绝仍可读取。设置覆盖优先;“跟随系统”沿用宿主语言选择规则,中文 UI 语言使用简体中文,其余使用英文。它与 system.locale(Windows 区域格式)分别定义,不返回原始语言偏好列表或用户身份。
组件通过 (await widgetHost.ready).language 获取初始值,通过 widgetHost.context.language 获取当前值。实际语言改变时宿主发送 language.changed,数据为 {language};SDK 先更新 context 再调用监听器。仅切换语言策略而实际语言相同时不重复发事件。隐藏组件同样更新;全局暂停期间宿主合并保留最新语言,恢复时才发送实际变化,不积压中间语言事件,也不为语言切换解除全局暂停。事件沿用当前文档会话检查,导航或关闭后的旧消息无效。组件只能读取,不能修改宿主语言。
await widgetHost.ready;
renderLanguage(widgetHost.context.language);
const off = widgetHost.on('language.changed', ({language}) => renderLanguage(language));
addEventListener('pagehide', off, {once: true});系统媒体会话
媒体读取与控制为基础接口,无需声明或授权;media.get 只读取当前会话,media.control 只控制最近读取且仍为当前的会话;接口、引用隔离、额度和取消以系统媒体会话为唯一权威定义。不提供全局输入或任意播放器控制,音量控制使用独立的基础接口。
透明场景的触摸与笔
功能标志 input.pointer表示宿主使用稳定 composition API 转发自身输入表面的触摸/笔消息,不表示机器有触屏或已通过所有设备验证。每实例最多 limits.inputPointers=8 个活动/悬停指针,无新增 Capability、全局输入监听或作者指定 pointerId 的接口。输入来自本实例输入 HWND,含坐标、触点与系统提供的压力/倾斜等标准网页输入;原生窗口/设备句柄不暴露。未知设备字段不伪装成功,缺失设备矩形时不补造 HIMETRIC 数据。
坐标按 WebView 物理内容区转换,DPI 只由浏览器映射为网页坐标;历史仅取当前一帧,不积压历史样本。只接受本输入窗口收到的消息,开始在自身交互区的触点可在离区后继续接收该次捕获。超出八个的序列不混入,释放后不从其中间更新重新开始。笔悬停可进入/离开,主触点或笔尖实际按下后的 500 ms 内可调用 beginDrag;笔侧键不获得宿主拖动资格。鼠标路径保持原语义,不以手柄或 IME 兼容性替代本项。
捕获丢失、取消、失焦、隐藏、重载、停用、窗口重挂载/尺寸变化和可信编辑会按下述规范化规则终结活动触点,不将取消当作正常抬起点击,不依赖成对的 UP/LEAVE。取消后旧接触的 UPDATE 不重新获得资格;新的真实 DOWN 可继续。宿主拖动仍只保留一个最新位移、每 16 ms 合并,临时位置不写布局或存档。稳定 API、取消后的网页事件及不误触必须以实际构建和真实 WebView 检查确认,状态见实现说明,不根据 SDK 名称或 DOM 合成事件宣称设备兼容。
input.normalizedCancel:正常按下、移动、抬起仍使用 composition 原生转发;宿主取消由文档创建前安装的适配器向原目标(有捕获时为捕获目标)发送 pointercancel,其 isTrusted=false,不能用作用户手势或权限证明。适配器在捕获阶段屏蔽该触点后续原生抬起及点击,新 DOWN 清除该 ID 的旧屏蔽;浏览器自身的可信取消保留。作者应处理取消而不要求取消事件具有用户手势资格。
适配器最多保留八个活动指针及八个待屏蔽 ID,无计时轮询或全局系统监听;不依赖作者是否载入 SDK。原生取消最多一项在途、最长两秒,完成前不接纳新的触摸/笔消息,成功后才结束浏览器内部接触;失败进入可重载故障,不发送可造成误点的普通抬起。导航及关闭使旧回调失效,关闭文档不向新文档补发输入。适配器是该网页的输入规范化,不是对恶意作者的安全隔离;作者调用其取消函数也只能取消本页面输入,不能产生系统操作或权限。
当前浏览器会给笔序列的末尾点击重新分配 pointerId,因此另为 touch/pen 各保留一个“最近真实按下是否已取消”的状态,在该类型下次可信 DOWN 前屏蔽其旧末尾事件;不屏蔽鼠标、键盘激活或作者脚本点击。新按下重置本类型状态,不能用无限时间窗拦截所有点击。
文件工具的PNG重新生成
文件统计模板的PNG工具在成功处理后,仅在本页面内保留一份已选原PNG的Blob(最多8MiB),供256/512/1024最长边切换后重新生成;重新生成按钮也可重试取消/失败,不重新读取本机文件或重开选择器。重算使用原始PNG,不从上一次缩略结果继续缩放,仍不放大、保留透明;每次最多一项在途/一个Worker,Worker10秒及读取/处理30秒目标期限保持。源Blob加本次传输缓冲各最多8MiB,输出最多5MiB,解码/画布沿既有4096边长及4194304像素限制;不声称整个浏览器的内存硬配额。重算开始撤下旧预览/导出,完成后才提供新结果;取消保留页面内源Blob用于手动重试,新选图(含取消选择)、配置/权限失效或离开清除源与结果引用及URL。源正文不写入缓存、存档、日志或统计导出,不新增权限;实际PNG导出仍走可信保存位置。释放引用不等于硬实时回收或内存擦除。
文件工具的本地文本预览
文件统计模板另提供本地UTF-8文本预览(filePreview,按显示器配置,默认关闭),仅用于用户选择或包内资源名以txt/csv/md/json/jsonl/ndjson/log结尾的文件;扩展名只是呈现提示,不改变宿主格式/权限校验。沿原files.read每次最多64KiB,TextDecoder跨块解码、无效字节显示替换字符,正文仅textContent显示,最多8192个UTF-16单元且不截断代理对;达到上限后停止解码但继续有界统计,不累计完整文件。空文本明确显示0单元,不加入JSON统计导出/复制、存档或历史。取消可保留此前已显示片段并明确不完整;中途读取失败或宿主返回CANCELLED时明确标记“读取失败/已取消;仅显示已接收片段,内容不完整”,不把截断或已显示的片段冒充完成。失败不得启用统计导出/复制,仍关闭本次句柄,下一次读取可恢复。此规则同时适用于网络预览。配置/权限失效、下一次统计及离开清空;默认关闭或非支持后缀不显示。网络预览仍独立由responsePreview及响应类型控制;两者均不执行预览内容,外部条件和原生授权边界不变。
剪贴板读写
所有现有Capability授权后直接调用,不增加宿主逐次确认。剪贴板提供可拒绝的clipboard大类;一次授权同时允许读写,拒绝不妨碍启用。复制与外部网址不逐次确认。文件导入/导出的实际选择、覆盖确认、秘密/目录绑定的范围选择仍是实际操作流程;第三方登录/同意由组件与服务自行处理。安装共享授权、执行和最终投递复核、撤权取消、直接联网及实例隔离保持;不开放任意命令、插件、路径或其他能力。
clipboard.readText {} 返回 {text:string|null},需要clipboard。只取当前CF_UNICODETEXT;没有可用文本格式返回null,空文本返回空字符串,不截断。最大32768 UTF-16单元、UTF-8最多65536字节,必须完整终止及有效Unicode;超额QUOTA_EXCEEDED,格式损坏/剪贴板忙RESOURCE_UNAVAILABLE。原文本换行保持;此方法只读文本,其他格式见丰富剪贴板,无历史或监听。浏览器原生剪贴板权限保持受控,不用浏览器许可绕过Services校验与有界接口。组件取得授权后可主动调用,也可能通过已允许的直接联网发送文本;可信授权入口要明确说明读取系统剪贴板。
clipboard.writeText {text} 返回 {},需要clipboard,授权后直接替换CF_UNICODETEXT。输入1..32768 UTF-16单元、UTF-8最多65536字节;拒绝NUL/不成对代理项,CR/LF规范为CRLF,原生结果最多65536单元。不读取或备份旧剪贴板,已开始清空/写入的副作用无法由取消撤回;清空后写入失败可能为空。
读每安装/全局滚动一分钟60/240次,写30/120次;授权和参数校验后保留额度。全宿主一个原生读取在途,忙时RESOURCE_BUSY,不积累线程。调用沿有界IO队列,原生句柄/内存RAII释放,不持Services事务锁;Services拥有授权、额度、失效票据及最终投递,平台不依赖服务模型。GetClipboardData可能触发外部所有者延迟呈现,不承诺硬取消时限;取消/撤权后迟到内容不得投递。读取不写日志、磁盘、缓存或存档,作者可显式处理返回值。
授权目录变更订阅
directory.watch.open {bindingId} 无需Capability,监听绑定根目录的直接子项,不递归、不接受本机路径或子目录参数。返回 {watchId,revision:"0",rescan:true};先建立监听,再完整调用 directory.list,避免先枚举后监听的空隙。directory.watch.poll {watchId} 返回 {watchId,revision,rescan:true}。revision 为仅在本句柄内比较相等的不透明字符串;与上次不同即重新枚举,不能解释为事件数量、文件事务或有序增量。所有通知(包括溢出)合并为一个待刷新位,首个变化后至少 200 ms 合并发布;持续事件不会无限推迟刷新。系统事件可能重复、合并或遗漏,不提供完整文件系统日志;作者还应提供手动刷新。
directory.watch.close {watchId} 取消监听,已关闭的自有句柄可重复关闭。句柄属于宿主提供的实例和当前文档,不能跨安装、跨屏或重载复用。每实例最多 2、全局 16 个,包含正在取消的监听;每个原生缓冲 64 KiB、最多一个未完成读取,无作者事件队列。空闲 30 秒、绝对寿命 5 分钟,到期明确失败,作者按需重建。建议每秒 poll;poll 不等待文件变化。不支持文件系统返回 RESOURCE_UNAVAILABLE。
移除绑定、停用、文档失效或取消使旧监听失效,重新启用须创建新监听。每次执行和投递复核实例与绑定代次;定期后台回收不依赖作者继续调用。根目录允许移动/删除,但下一次检查发现路径、身份或链接变化即关闭,不跟随到新的范围。目录内容变化仅触发原有受控枚举,不返回外部路径或文件正文;隐藏仍继续监听。
Services 唯一拥有监听注册表、资源绑定与生命周期,平台只持受限句柄及同步有效性回调。打开、检查、回收都在后台;退出先停止工作队列,再在后台取消并等待原生 IO 完成,最后释放 OVERLAPPED 和缓冲,不在 UI 等待。取消是逻辑立即失效,驱动完成没有硬实时保证,不提前释放内核仍使用的内存。
带秘密绑定的 HTTP 流
network.open {url,method,headers,bodyBase64?} 与 network.request 共用 URL、方法、秘密解析和重定向规则,收到最终响应头后返回 {streamId,status,headers,finalUrl},不预取正文。无需网络 Capability,不改变直接联网。请求正文仍最多 1 MiB;HTTP 头最多 64 项,每个解析后的值最多 32768 UTF-16 单元,传输头限制及跨源后永久移除敏感头保持。
network.read {streamId,offset,length} 每次拉取 1..65536 字节,返回 {offset,bodyBase64,eof};offset 从零开始,只接受下一未读偏移,或完全相同 offset/length 重试上一块。一次只保留上一块,不提供任意 HTTP seek,不自动重发整个请求。短块不代表 EOF,WinHTTP 返回零字节才表示结束;消费后再取下一块。响应正文累计最多 256 MiB;每实例最多两条、全局十六条,额度在开始网络 IO 前保留。每条同时最多一个 read,忙时 QUOTA_EXCEEDED。
network.close {streamId} 释放自己的流,已释放可重复关闭。流绑定原实例、原文档、秘密绑定失效代次;空闲三十秒/绝对五分钟回收。逐块和最终投递前检查原秘密与生命周期;撤销再授予、替换秘密、重载或停用不能复活旧流。read 执行中取消/失败使该流失效,避免已消耗字节产生隐蔽空洞;作者在 finally 关闭,未开始执行的取消不能当作已经释放。
同步 WinHTTP 的连接/发送/接收阶段配置三秒超时,open 总检查预算三十秒;关闭/取消先标记失效,待在途 WinHTTP 调用返回才实际关闭句柄,不在另一线程强关正在同步使用的句柄。检查在原生调用边界生效,不能承诺立即打断 DNS、系统代理或驱动调用。所有流 IO 和回收在有界后台队列,UI 只做短身份/代次查询,不等待原生网络。
现有 network.request 保留四 MiB 整块响应预算,复用相同连接/重定向实现。流不拼接成无限增长的 KV 或字符串;作者可逐块统计、解析或写入已有定额导出资源,导出仍需要可信系统保存选择。具体实际支持见 implementation.md。
实例存档
saves.list/load/commit/restore 的预算、修订冲突、数据版本及故障恢复以实例存档为唯一权威定义。属于自身数据基础功能,不增加 Capability;不受布局恢复影响。
可信文件导出
导出先在宿主私有临时资源中准备完整内容,再请求可信系统保存选择;网页不能指定本机路径。exports.begin {name,sizeBytes} 返回 {exportId},name 为不含路径的建议文件名(最多 128 UTF-16 单元),仅允许 txt/json/csv/png/jpg/pdf/bin 扩展名,拒绝设备名、控制字符、末尾点/空格与原生 MZ 载荷。内容不会由宿主执行。sizeBytes 为 0..67108864 整数,每实例最多两个、全局十六个导出资源,声明总量全局最多 256 MiB;空闲 30 秒/绝对五分钟期限。资源绑定实例、原文档及服务失效票据,不进入存档、布局或永久缓存。
exports.write {exportId,offset,bodyBase64} 每块 1..65536 字节,offset 必须为 0..已写长度的整数,不能留空洞或超出声明长度;返回 {writtenBytes}。明确 offset 允许丢失响应后覆盖同一块重试;每资源只准一项在途 IO,忙时 QUOTA_EXCEEDED,不预取或接受无限积压。取消原生待决 IO 后等待其完成才释放缓冲。作者消费/生成一块后再写下一块,不先拼出完整文件。
exports.save {exportId} 仅在全部声明字节写完后允许,封存内容并展示系统保存选择器,含覆盖提示、固定扩展名过滤和当前组件名称。取消保持原文件及封存内容,允许重试保存;exports.close {exportId} 可取消/释放,作者在 finally 调用。目标必须是普通本地磁盘文件或新文件,不接受目录、重解析叶节点或网络共享;保存先在目标目录创建独立暂存文件,按块复制及检查有效性、刷新后最后一次检查,再同目录重命名提交,之前不截断或删除原文件。失败清理本次暂存,取消发生在提交开始前保留原文件;原子提交开始后的取消不能撤回外部结果,未收到响应不能断定文件未保存。成功返回 {name,sizeBytes},不返回路径,并释放导出资源;用户输出不随组件卸载删除。不承诺电源故障或所有第三方文件系统的事务保证。
导航、停用、卸载、期限或服务退出使未保存资源失效并回收。资源登记/IO 由 Services 拥有并使用有界外部队列,UI 只收集保存选择,不在 UI 或事务锁执行复制、刷新和关闭文件。导入与导出分别计数,不授予目录写入、任意文件写入、命令或插件能力。
自主移动与生命周期
window.moveTo {x,y} 接受当前实例工作区内的 DIP 位置(有限且绝对值不超过 1000000),宿主按实际尺寸夹在当前屏幕工作区。已启用且可见的实例默认可以调用,无需声明 Capability 或额外授权开关;不能移动其他窗口、切换显示器、改变层级或抢焦点。
接受后返回空对象,表示最新位置进入一个槽位;即时接受即完成请求,之后的 Abort 不撤回已接受位置。原生窗口最多每 16ms 应用一次,后到位置覆盖尚未应用的位置,不逐帧保存元数据。实际位置通过 environment.changed 返回;临时位置与 window.beginDrag 一样不进入布局或游戏存档,用户保存的位置不变。
隐藏、可信编辑、全局暂停、锁屏、系统挂起或正在拖动时拒绝新移动并清除待应用位置,返回 RESOURCE_UNAVAILABLE;这些状态解除后不重放旧请求。编辑、重载或已保存锚点变化时恢复保存位置。锁屏与电源挂起原因独立,解锁不能提前解除仍有效的挂起保护。原生 16ms 合并仍受桥接总量和消息速率限制,不承诺恒定帧率。用户可通过可信编辑和真实拖动调整位置。
包内资产范围读取
assets.open {path} 只打开当前已激活包中的普通文件,返回与 files.open 相同的 {fileId,name,sizeBytes},随后使用 files.read/close。path 是不解码的包内相对路径,最多 512 UTF-16 单元,使用 /;拒绝空段、.、..、反斜线、冒号、控制字符和末尾空格/句点,不接受 URL 或本机路径。祖先目录和叶文件拒绝重解析点,打开后核对实际路径归属;读取不执行脚本、Wasm 或原生代码。
资产与用户导入共享每实例十六个/全局十六个句柄、单资源 256 MiB、64 KiB 块、单句柄背压与期限。Services 从可信实例确定当前包和失效票据,绑定原文档;导航、包激活或停用使旧资源失效。后台执行,不持事务锁打开文件,不向网页返回包目录。正常包 URL 仍可用于浏览器图片/媒体加载,此接口用于明确按需读取二进制内容,不替作者累计整个资源。
用户选择的文件与有界读取
files.open {multiple?:boolean} 请求可信系统文件选择器。multiple默认false,仍返回单个 {fileId,name,sizeBytes};multiple=true允许多选,返回 {files:[{fileId,name,sizeBytes},...]},不是独立方法。取消选择返回CANCELLED,不返回空成功结果。未知字段或非布尔multiple拒绝。选择器同时最多一份,取消、超时、导航或停用后关闭;不接受网页起始路径、脚本或原生对象,不暴露完整路径。
多选与拖入每批1–16个文件,单文件及整批总大小均不得超过256 MiB;任何一项无效、批次超限、取消或失效均整批拒绝,释放所有暂存句柄,不交付部分成功。只接受普通磁盘文件,拒绝目录、虚拟文件、叶节点重解析点或不可读文件。所有检查及打开在有界后台队列执行,Services限定实例和原文档,文件在完整校验及配额检查后一次性发布;网页只能逐块读取,不预先复制正文。用户导入和包内资产共享每实例16个、宿主全局16个活跃句柄,暂存批次先预留同一额度;空闲30秒或绝对存活5分钟后失效,句柄保持只读共享。资源不进入配置、布局、KV或存档。
文件拖入
files.setDropEnabled {enabled:boolean} 无权限设置当前文档是否接受文件拖入,默认false,成功返回 {}。作者先监听 files.dropped 再启用;事件数据为 {files:[{fileId,name,sizeBytes},...]},每次最多一批在途。files.dropFailed {code,message} 报告本次拒绝,不包含路径或正文。禁用只阻止新的拖入并取消尚未交付的批次,已交付句柄继续按原文档和期限有效;重载、停用及关闭清除拖入设置,旧文档不能向新文档补发。
SDK仅处理当前主文档的可信DOM文件拖入事件,不处理组件脚本合成的drop;内部桥接使用WebView2原生File附加对象取得路径,不接受JSON路径字符串或脚本构造的内存File作为磁盘文件。原生对象仍需服务重新检查,事件不作为后续能力的物理输入证明。普通窗口使用WebView2原生拖放,合成窗口在实际可交互表面转发OLE文件拖放;透传区域及编辑遮罩不扩大接收区。隐藏、全局暂停、编辑、会话锁定/挂起或关闭时不接收新批次。拖入只做只读导入,源文件不移动、不删除,不递归导入目录。
await widgetHost.ready;
const offDrop = widgetHost.on('files.dropped', ({files}) => consumeFiles(files));
const offDropError = widgetHost.on('files.dropFailed', ({code}) => showImportError(code));
await widgetHost.call('files.setDropEnabled', {enabled:true});
const {files} = await widgetHost.call('files.open', {multiple:true});
// 每个fileId仍通过files.read按范围读取,并在finally中files.close。
window.addEventListener('pagehide', () => { offDrop(); offDropError(); }, {once:true});files.read {fileId,offset,length} 读取明确字节范围;offset 是 0..sizeBytes 的整数,length 为 1..65536 整数。返回 {offset,bodyBase64,eof},最后一块可短于请求,offset=sizeBytes 返回空内容及 eof=true。每句柄最多一个在途读取,忙时返回 QUOTA_EXCEEDED;没有推送或预取,宿主只分配一块缓冲。显式 offset 使取消或未收到响应后的重试不跳字节。每次读取在后台检查实例、原文档、资源期限、请求取消及最终投递代次;取消只影响本次读取,不让过期资源复活。
files.close {fileId} 释放自己的资源,重复关闭自己的已消失句柄成功;不允许关闭或读取其他实例资源。导航、停用、卸载和服务退出失效并回收资源;过期回收在有界外部 IO 队列执行,不在 UI 或 Services 事务锁内关闭文件。请求取消会取消未完成的原生读取,驱动完成取消前继续保留其缓冲和句柄,不承诺所有设备驱动的硬取消延迟。IO 队列、请求总量与最终投递规则沿用本文其余约束。
作者逐块拉取和消费后再请求下一范围,取消时在 finally 关闭句柄;不得把大文件先拼成一个无限增长的字符串或 KV 值。导出和带秘密绑定的 HTTP 流分别见本页对应定义。
本协议定义不可信网页与原生宿主的边界。组件不能直接访问宿主对象、Steam SDK或任意本机API;网络和执行策略见下节。兼容代际及升级遵循格式版本规则(宿主文档:../development.md#格式版本兼容与迁移)。
实际执行及最终投递均校验撤权与失效代次,慢IO使用全局有界队列。
宿主提供 context.system、context.lifecycle 与 runtime.info 支持标志。握手的 system 为有限上下文:{theme,accentColor,textScaleFactor,locale,reducedMotion,highContrast,power:{source,energySaver},sessionLocked}。theme 为 light/dark/unknown,locale 为系统默认区域语言标签或 null,source 为 ac/battery/unknown;reducedMotion、highContrast、sessionLocked及energySaver为布尔或null,accentColor与textScaleFactor见强调色与文字缩放;unknown/null 表示尚未取得或不可用,不推断成功。主题为系统偏好,不是宿主管理页的主题覆盖。后台最多每两秒采样一次,当前会话锁定状态只用于生命周期,不返回会话 ID、账号、输入、前台应用、精确电量或电池寿命。采样使用有界外部 IO 队列,每实例最多一项,队列满后下周期重试;不持有 Services 事务锁。
值变化发送 system.changed 完整快照,SDK 先更新 context。lifecycle.changed 的 {state} 为 suspending/resumed/locked/unlocked,来自当前安装宿主窗口收到的电源与当前会话通知;不保证所有过渡均可观察,不区分睡眠与休眠,不承诺睡前异步存档必然完成。恢复只发送一次自动恢复事件,不因随后的用户唤醒消息重复结算。通知不自动停用、隐藏或暂停模拟,不更改授权。导航后丢弃旧文档采样,关闭后解除通知;锁屏/休眠释放正在进行的宿主拖动捕获并保存已到达的位置,不自动重新捕获或抢焦点。
握手 runtime 为 {engine:"webview2",version,platform:"windows",architecture:"x64"},version 来自实际创建的环境,读取失败为空字符串。功能目录只表示宿主支持;SDK compatibility() 返回当前主文档的 API 存在探测、真实 WebGL2 上下文创建结果和该宿主信息,不包含 GPU 型号/渲染器指纹,不声称音频已播放、Worker 已运行或所有设备兼容。真实执行检查仍须使用作者内容和必要测试(宿主文档:../testing.md)。compatibility({signal}) 可取消握手等待,取消仅结束本次报告,不取消共享 ready。apis.gamepad 只检查 navigator.getGamepads 是否为函数,访问异常视为不可用;devices.gamepadInput 固定为 not-tested。不调用该函数、不读取身份或按钮、不订阅连接事件或轮询,接口存在不能证明焦点、权限策略或实体设备输入可用。依据 MDN getGamepads 与 gamepad 权限策略,不新增宿主 Capability 或输入转发承诺。
授权语义
manifest 只声明可能调用的能力;权限记录位于宿主,按 installationId 共享。首次启用展示能力选择,全部拒绝仍启用。未声明、未授权、已撤销的能力均不能调用,不能通过运行状态、来源、本地配置或布局绕过。
安装新版时新增能力保持未授权,不弹强制授权流程、不阻止运行;管理页显示可授权项。每次受限调用在实际执行前检查,长操作在交付结果前再次检查。撤销后取消可取消的请求和订阅,丢弃未交付敏感结果;已经完成的外部打开或已发送到服务器的请求不能被撤回。
宿主为桥接请求记录内部有效性票据,包含实例生命周期、相关能力的撤销代次和实际使用的绑定。票据只记录失效代次,不能代替后台实际授权;捕获与最终投递不等待事务磁盘 I/O。撤销后重新授权不能恢复旧请求;秘密替换、绑定清除、实例启停或版本切换同样使相关旧请求失效;重新启用也使停用期间的可信管理票据失效。最终向 WebView 投递成功结果与服务撤销使用同一短同步边界:先撤销则返回 CANCELLED,先完成宿主消息投递则属于已交付,不能收回。初次未授权和无效绑定仍分别使用原有能力错误与 RESOURCE_UNAVAILABLE。无关配置或权限修改不取消其他有效请求。directory.list/read 仅以 bindingId 选择资源,传入仅属于 target.open 的 directoryBindingId 返回 INVALID_ARGUMENT,避免执行与票据使用不同绑定。
组件自有加密凭据无需Capability,适用边界见组件凭据。
权限与基础接口
只有以下6项属于manifest.capabilities,默认拒绝、按安装授权;全部拒绝仍可启用。
| Capability | 界面名称 | 授权范围 |
|---|---|---|
| external.open | 外部网址 | 在默认浏览器打开HTTP/HTTPS地址 |
| location | 地理位置 | WebView定位,同时受Windows定位服务限制 |
| notifications | 通知提醒 | 即时通知与持久提醒 |
| clipboard | 剪贴板读写 | 文本、HTML、图片读写及文件导入,无监听或历史 |
| directory.write | 授权目录读写 | 对用户绑定目录增加写入、创建、重命名和单项删除;基础读取不受此开关控制 |
| screen.capture | 显示画面采集 | 系统选择器选定窗口或显示器,短期有界会话 |
下列9项为基础接口,无需声明或授权,遵守各自的资源范围、额度、实例/文档生命周期、取消和最终投递规则。
| 基础功能 | 方法 | 范围 |
|---|---|---|
| 系统指标 | system.metrics.get | CPU、内存、磁盘和网络汇总,无进程列表 |
| 目录读取 | directory.list/read/image/watch.* | 仅用户绑定目录,保留路径、文件身份及绑定失效检查 |
| 打开绑定目标 | target.open | 仅用户选定目标或绑定目录中的普通文档 |
| 媒体控制 | media.get/control/artwork | 当前媒体读取及最近读取会话的有限控制 |
| 系统音频采集 | audio.capture.* | 默认输出混音,不采集麦克风 |
| 光标位置 | desktop.pointer.get | 所属显示器位置,无输入内容或历史 |
| 窗口位置 | desktop.windows.get | 所属显示器窗口几何,无标题、正文或窗口控制 |
| 音量控制 | audio.output.get/set | 默认输出音量与静音,与组件静音分开 |
| 活动状态 | system.activity.get | 当前会话活跃/空闲状态,无输入内容或历史 |
location需要独立声明与授权。网页使用标准 getCurrentPosition / watchPosition。定位 PermissionRequested 只允许当前实例的包来源,执行时检查声明、安装级授权与启用状态;不把允许状态保存到浏览器 profile。定位授权变化时重建该安装的运行 WebView,停止旧文档定位请求与监听,保留配置、启用及可见性。撤销不能收回已经交付的位置或已发出的网络请求。系统定位关闭、服务无结果、用户拒绝或超时都不能阻止启用。
配置、实例 KV 存储、读取自身上下文、window.setSize 和 network.request 是基础接口,不需要弹窗授权,但严格限定自身实例。这些接口不允许读取任意文件或其他组件数据。
target.open 不接受组件传来的程序路径、命令行或 shell 字符串。应用/快捷方式绑定必须由用户在可信 UI 中选择;文件绑定拒绝脚本与可执行载荷的伪装。既有快捷方式的目标由宿主向用户显示后绑定。使用 directoryBindingId 的形式只允许普通文档,不允许直接启动目录中的可执行文件、脚本或快捷方式;这些目标须经专门的启动绑定选择流程。
网页与网络边界
允许WebAssembly编译,不增加系统授权;Wasm字节来源和宿主保证边界以组件包协议为准。
包资源通过实例专属本地虚拟 origin 提供,主文档锁定为包内页面。拒绝包外 file URL、跨实例资源、远程顶层导航、子框架、弹窗、新窗口、下载、外部协议跳转以及摄像头/麦克风等未提供的浏览器权限。定位仅通过上表的 location 能力开放,其他浏览器权限仍默认拒绝。
组件默认可联网。 网页直接 fetch/XHR、HTTP(S) 图片/字体/媒体/样式、WebSocket、EventSource、WebRTC、Beacon 和数据预取可以使用;遵循浏览器 CORS、TLS 与混合内容规则,不承诺跨源请求必然成功。可访问用户网络可达的本机、局域网和外网,不做目的域名白名单。Shared/Service Worker、子框架、表单导航、远程顶层页面及远程可执行脚本仍不开放。network.request 是可选的宿主 HTTP(S) 数据接口,允许作者使用秘密绑定等宿主服务。
宿主在交付响应时注入受控 CSP(JavaScript 仅包内,禁 frame/object,Dedicated Worker 仅包内、禁 JavaScript eval/Function 和内联脚本,使用 wasm-unsafe-eval 允许 WebAssembly 编译;连接允许 HTTP(S)/WS(S),图片、字体、媒体和样式可使用 HTTP(S),样式可内联),并配置 WebView 事件、权限和资源拦截策略。网页不能删除宿主响应头策略。WebAssembly 不增加宿主能力或改变授权检查。宿主提供的 SDK 将用户点击的外链转到受控 external.open;其他外部导航由宿主阻止,具体规则见下文。网络代理允许标准 HTTP 方法、验证 TLS、最多跟随 5 次 HTTP(S) 重定向,每跳重新检查当前有效会话与运行状态;不自动附加 Windows 身份、其他浏览器 Cookie 或系统代理凭据。
上述是剩余宿主边界与必须验证的实现策略,不构成断网或防外传隔离。联网及宿主/代码边界的改动按 必要测试(宿主文档:../testing.md) 选择相关检查。诊断中仍区分 WebView 自身流量和组件请求。
会话与消息
宿主在主文档创建时提供SDK,由SDK发送 host.ready;宿主验证该 WebView、当前顶层来源和文档会话后,返回 sessionId 和自身上下文。sessionId 是防止旧文档消息串线的令牌,不是授权凭据。实例身份始终来自宿主持有的 WebView 映射,不能相信消息中的自报身份。
桥接采用异步 JSON 请求/响应;宿主只接受当前主框架、当前源、当前会话的消息,导航或重载后使旧会话及请求失效。不注册通用原生对象或可反射执行的方法。SDK 概念调用为 await widgetHost.call(method, params),最终 JavaScript 封装遵循此消息格式。
{
"protocolVersion": 1,
"id": "42",
"sessionId": "host-issued-session",
"method": "notifications.show",
"params": {"title": "任务完成", "body": "处理结果已准备好"}
}{
"protocolVersion": 1,
"id": "42",
"sessionId": "host-issued-session",
"ok": false,
"error": {"code": "CAPABILITY_DENIED", "message": "未授予 notifications"}
}成功响应使用 ok: true, result: ...,错误响应只含 error;同一请求只能完成一次。id 是无前导零的十进制正整数字符串(1 至 9007199254740991),严格大于当前文档此前已接收序号,握手也占用序号。重复、倒序和旧式字符串拒绝,不保留历史 ID。bootstrap 的 method 为 host.ready、sessionId 为 null;回复包含新会话和 {apiMajor,features,limits,instanceId,displayId,visible,language,theme,i18n,environment,system,audio,runtime,configuration,capabilities},capabilities 仅列声明能力的授权。数据就绪前仅接受 host.ready。
前四种事件的 data 分别为完整配置、能力到布尔值的映射、{visible}、{displayId};其他事件见相应上下文定义。握手提供初始快照,不保证再发同值事件;同一 UI 队列按提交后的观察顺序发送,跨请求响应与事件不保证固定先后。SDK 先更新 context 再通知订阅者。
请求顺序与预算
协议变更遵循格式版本规则(宿主文档:../development.md#格式版本兼容与迁移)。features 返回支持的方法名及 webassembly、location、events、context.visibility、request.cancel、directory.snapshot、network.safeRedirect;实现支持不代表授权。features供作者探测支持情况并提供降级。所有Capability仍可拒绝。
解析前每 WebView 限 60 条/秒、突发 16 条,8 MiB/秒、突发 16 MiB;全宿主限 240 条/秒、突发 64 条,32 MiB/秒、突发 64 MiB。超额不解析、不回应不可信 ID,SDK 等待会超时;单条仍限 8 MiB。limits 返回这些值和并发、超时、正文、KV、目录额度。获取 WebView 原始消息的开销无法由预算消除。
request.cancel({requestId}) 取消当前会话未交付请求,返回 {};目标返回 CANCELLED,不存在也成功,不撤回已发生副作用。SDK call(method,params,{signal}) 在 AbortSignal 取消时结束本地等待并尽力发送取消;卸载释放计时器和订阅,导航负责实际失效。
AbortSignal 从 call 开始等待握手时即生效;预先取消或握手期间取消立即以 CANCELLED 结束该调用,不发送业务请求、不取消共享 host.ready,也不影响其他等待者。握手失败仍传给未取消的调用;所有完成路径解除该调用的 abort 监听。
重定向以 scheme、大小写不敏感 host 和有效 port 判定同源。同源保留头;第一次跨源后永久移除 secretBindingId 头和 Authorization、Cookie,不因跳回原源恢复。原请求含这些敏感头时 HTTPS→HTTP 拒绝 RESOURCE_UNAVAILABLE;普通请求仍可降级,首次显式访问任意 HTTP(S) 不变。联网与绑定凭据的使用规则见配置协议。
宿主提供的 SDK 将真实用户点击的 HTTP(S) 外链(含 target=_blank)转到 external.open,并阻止原导航;脚本导航和弹窗仍由宿主拦截。错误经 SDK error 事件反馈。
目录分页一次有界枚举生成快照:最多扫描 10000 条原始记录、最多 10000 项和 8 MiB,超出返回 QUOTA_EXCEEDED,不截断成功。每页最多 100 项;cursor 是绑定实例、绑定 ID、目录身份及相对路径的随机一次性令牌,绝对有效期 30 秒,可用 directory.cancel({cursor}) 释放。每实例最多 4 个快照,全宿主最多 32 个、32 MiB;过期回收,容量不足返回 QUOTA_EXCEEDED。后续页不重扫目录,返回首次枚举记录;枚举不是文件系统事务,读取/打开仍检查当前文件。过期、跨实例、路径不符或已消费游标返回 RESOURCE_UNAVAILABLE;权限、绑定或实例失效后不能继续取页。
宿主管理计时器每秒尝试回收过期及生命周期失效的快照,即使没有后续 list、窗口收起或管理事务忙碌也执行;遇缓存锁忙则下一周期重试。失效包括文档导航/关闭、实例停用、能力撤销和绑定移除,重新授权不复活旧快照。此周期不是实时释放 SLA;退出销毁缓存释放剩余条目。保留的有效性回调只检查失效代次和原子生命周期,不获取事务锁或执行 IO。
接口表
透明交互区域与拖动
功能标志input.polygons支持多边形区域。window.setInputRegions 的每项为矩形 {x,y,width,height} 或多边形 {points:[{x,y},...]},两种字段不得混用。总区域仍最多 64;每个多边形 3–32 个顶点,一次请求的多边形顶点总数最多 512,额度分别为 inputPolygonPoints / inputPolygonVertices。顶点顺序沿边界,闭合边由宿主补齐,首点不重复;支持顺逆时针与凹形,拒绝重复点、自交、非相邻边接触、相邻边回折、零面积和超出自身内容。坐标为有限非负 DIP,先量化到最近 1/64 DIP,量化前后都必须位于内容内;量化后退化同样拒绝。形状取并集,不支持洞或相减。非法参数为 INVALID_ARGUMENT,数量超额为 QUOTA_EXCEEDED;失败原子保留旧区域。矩形现有取整规则保持;多边形转为最近物理像素后裁剪到当前窗口,亚像素细部可能消失,边界遵循系统区域命中,不承诺数学边线像素。更新、取消、编辑恢复和文档归属沿用本节规则,不进入布局或存档。
透明场景支持 window.setInputRegions 和 window.beginDrag 功能标志,无新增 Capability。window.setInputRegions 仅用于透明且 inputMode=regions 的当前实例;矩形规则为有限 DIP、非负起点、正尺寸且完全位于自身内容范围,多边形规则见本节首段;不接受其他实例或显示器。替换原子生效,失败保留旧区域,空集合纯穿透;导航/重载清空,调整尺寸/DPI时重新计算原生区域并裁剪到当前内容。可信编辑期间拒绝作者区域变更,编辑遮罩始终接收完整矩形。
window.beginDrag({}) 仅在透明交互实例自己刚收到真实左键或主触点/笔尖按下的 500 ms 内、该次接触/捕获仍有效且非可信编辑时允许,其他情况 RESOURCE_UNAVAILABLE。鼠标捕获或触点的系统隐式捕获持续至松开、捕获丢失、隐藏、锁定、挂起或关闭,离开命中区仍可结束拖动;不能请求任意外部窗口输入。位置限制在当前工作区,原生每帧最多处理一个最新位置,结束时发送自身空间变化;拖动中不推送无界坐标流、不写布局,重载恢复保存位置。可信编辑模式清除临时位置并从保存位置操作,用户编辑仍由 Services 保存。返回均为 {},未就绪/故障时不能调用。
省略的 params 为 {}。bindingId 必须属于当前安装;所有实例数据由当前会话推导归属。
| method | params | result |
|---|---|---|
| config.get | 无 | 当前配置对象;秘密和资源仅返回 bindingId 或 null |
| config.set | {values: {...}} | 验证并提交后的完整配置;仅允许普通字段 |
| window.setSize | {width,height} | {width,height},当前实例作者请求的 DIP 尺寸;无额外 Capability,无位置或其他实例参数 |
| window.setInputRegions | {regions:[矩形或多边形]} | {};透明 regions 模式,形状与额度见本节 |
| window.beginDrag | {} | {};仅自身真实主输入捕获,临时位置不持久化 |
| storage.get | {key} | {found,value};不存在时 found=false、value=null |
| storage.set | {key,value} | {};value 为 JSON |
| storage.remove | {key} | {};不存在也成功 |
| network.request | {url,method,headers,bodyBase64?} | {status,headers,bodyBase64,finalUrl};HTTP 非 2xx 仍是网络响应 |
| system.metrics.get | 无 | {sampledAt,cpuPercent,memoryUsedBytes,memoryTotalBytes,disks,networkReceiveBytesPerSecond,networkSendBytesPerSecond} |
| directory.list | {bindingId,relativePath,cursor?} | {entries,nextCursor};entry 含 name、relativePath、kind、sizeBytes、modifiedAt |
| directory.cancel | {cursor} | {};释放当前实例的分页快照,已释放游标可重复取消 |
| directory.read | {bindingId,relativePath} | {bodyBase64};只读普通文件 |
| request.cancel | {requestId} | {};取消当前文档请求,未知或已完成请求不作操作 |
| notifications.show | {title,body,actions?} | {notificationId};系统通知及动作见桌面扩展,不保证用户看到 |
| target.open | {bindingId} 或 {directoryBindingId,relativePath} | {};表示已交给系统,不代表目标程序运行成功 |
| external.open | {url} | {};授权后直接交给浏览器 |
network headers 是数组,每项为 {name,value} 或 {name,secretBindingId,prefix?},二者互斥。宿主解析秘密引用并发送,不直接将秘密值作为接口结果返回网页;持有有效 secret 绑定的组件可通过该接口请求发送到任意 HTTP(S) 目的地,远端也可能在响应中反射它,因此不宣称此机制能防止组件外传凭据。禁组件自行控制 Host、Content-Length、Connection 等传输层头,不共享 Cookie jar。
window.setSize控制作者尺寸。宽高必须为有限正 DIP 数值、不小于作者声明的最小尺寸,且换算为物理像素后可由 Win32 表示;不接收位置、显示器或实例身份。成功后保存当前实例尺寸,保持启用、可见性及层级,宿主异步更新窗口;重启保留最后作者请求。失败不改变状态。小屏不自动缩小作者尺寸,仅约束位置,过大的组件允许部分超出工作区。用户及布局不能指定新尺寸;布局中的 width/height 仅为历史格式与预览记录,应用或恢复时使用当前作者尺寸,新实例使用包默认尺寸。旧宿主对此方法返回 METHOD_NOT_FOUND。
await widgetHost.call('window.setSize', {width: 360, height: 420});系统指标时间为 UTC ISO 8601,CPU 为整机百分比,内存与容量为字节,网络速率为字节/秒。disks 元素为 {id,label,totalBytes,freeBytes},id 为不透明标识,不包含真实路径。不可获得的单项为 null,首次速率采样为 null;不能用零伪装采样失败。宿主集中采样缓存,多个组件不会重复创建独立高频采集器。
目录 relativePath 使用 /,空字符串表示根目录。拒绝绝对路径、..、设备路径、ADS、链接/reparse point 逃逸。校验规范化路径并以打开后的文件句柄验证仍在授权目录内,防止检查与使用之间替换目标;不递归进入符号链接。目录被移动、权限变化或断开时明确失败,不自动扩大范围。
限额与错误
用户锁定KV写入输入优先校验:storage.set通过既有实例有效性、取消及安装级顺序检查后,先按JSON序列化的UTF-8字节检查新值的256 KiB单值限额;超额立即返回QUOTA_EXCEEDED,不读取/创建KV目录、不扫描或发布大小索引、不改变已有记录。即使磁盘另有损坏,本次超额输入仍优先报QUOTA_EXCEEDED;合法输入的首次写入继续完整扫描并校验所有记录与总额度,磁盘异常错误和失败保留规则不变。
实例KV的磁盘记录读取沿用当前写入限制:冷索引重建逐条检查键/值及累计配额,超限停止并丢弃候选索引;目标读取也拒绝超限存活值。键长或配额异常的磁盘记录返回RESOURCE_UNAVAILABLE,不把部分扫描结果用于提交;正常set超过额度仍返回QUOTA_EXCEEDED,既有值保留。内部扫描与取消边界见开发规范(宿主文档:../development.md)。
已锁定目标校验删除:大小索引尚未加载时,storage.remove仅范围读取目标键,沿storage.get校验JSON、键身份、found类型及存活值额度;目标损坏或不可读仍拒绝,目标不存在或已是有效墓碑则成功。无关记录损坏不再阻止冷删除。存活目标仍先原子提交墓碑再尽力回收;取消或提交失败保留原值。冷删除不构造或发布大小索引,后续合法首次storage.set仍完整扫描、验证所有记录和总额度。已加载索引的增量删除保持,不承诺物理磁盘时延或关闭首次写入性能问题。
以下为宿主接口默认限额,不是 WebView 或 Steam 官方限制:桥接消息 UTF-8 编码后最大 8 MiB,先检查大小再解析;单会话最多 16 个未完成请求,以每秒 60 次、突发桶容量 16 次限流;请求默认 30 秒超时。网络请求正文上限 1 MiB、响应正文上限 4 MiB;文件读取上限 4 MiB,目录每页最多 100 条;实例 KV 每值最大 256 KiB、总计 16 MiB。Base64 计入桥接消息大小。流式 HTTP 和范围读取使用前述独立句柄及限额。这些额度及取消机制只覆盖宿主请求,不覆盖网页直接联网;停止实例需释放 WebView,已经发生的网络副作用不能撤回。
返回 INVALID_ARGUMENT、PROTOCOL_UNSUPPORTED、METHOD_NOT_FOUND、CAPABILITY_UNDECLARED、CAPABILITY_DENIED、RESOURCE_UNAVAILABLE、NOT_FOUND、QUOTA_EXCEEDED、TIMEOUT、CANCELLED 或 INTERNAL_ERROR。公开错误不包含堆栈、秘密和个人路径。未知方法不回退到反射调用。请求超时/导航/停用/撤销时取消后台工作;无法取消的副作用在执行前检查,在结果返回时报告真实状态,不伪造撤销成功。
慢宿主 I/O 另受全局调度额度约束:4 个执行槽,每安装最多同时执行 2 项,执行与等待合计全局最多 64 项、每安装最多 16 项;不同安装轮流取得空闲槽。额度不足返回 QUOTA_EXCEEDED,等待时间计入原有 30 秒期限,失效任务在执行前丢弃。此额度不控制网页直接联网。包资源加载使用独立队列,不与网络、目录和启动操作竞争执行槽,具体规则见运行与窗口。
异常输入、重复请求和高速循环不弹系统模态框。宿主限流并返回错误,必要时标记实例诊断,由用户重载。组件作者必须为每个受限操作处理失败和缺失能力,避免空白页与无限重试。
包内计算与内存资源
允许包内 Dedicated Worker(classic/module),没有直接宿主桥接;计算结果由当前主文档处理。Shared Worker 在原生浏览器环境用 Blink SharedWorker 功能开关禁用;加载任何作者内容前在原始空白文档核验构造器未暴露,配置失效或核验失败则关闭该 Controller 并报告运行环境不支持,不静默放宽策略。此核验只读取浏览器原生 API,不覆盖 JavaScript 构造函数。原生请求来源继续拒绝 Shared/Service Worker 请求;Sec-Fetch-Dest 仅作可用时的补充,不能作为唯一保证。所有包内 JavaScript/MJS 响应均注入 CSP(Worker 入口可能被报告为 OTHER):脚本仅包内、禁止 eval/Function,允许 Wasm,禁止嵌套 Worker,仍允许直接联网数据。导航/关闭释放原文档 Dedicated Worker 与其在途包资源;不承诺浏览器线程硬配额。
图片与音视频允许 blob:/data: 内存资源;脚本、Worker、文档与框架不得使用这些来源。blob URL 的创建与及时 revoke 由作者负责,不代表宿主可对浏览器内存强制配额。网页用实际 API/上下文创建结果探测 WebGL2、OffscreenCanvas 与媒体支持,features 仅报告宿主策略,不保证 GPU 或设备可用。此节是开发契约,实际支持以实现说明为准。
自身空间上下文
握手提供 environment: {scale,editing,workArea:{width,height},bounds:{x,y,width,height}},空间均为当前显示器工作区 DIP,自身位置相对于工作区原点;editing 表示当前实例是否受可信编辑遮罩控制。不返回系统绝对坐标、设备路径或其他窗口信息。首次握手提供当前值;工作区、DPI、自身位置/尺寸或编辑模式改变时发送 environment.changed 完整快照,SDK 先更新 context;原生场景拖动在结束时发送,拖动中不推送逐点事件。features 为 context.environment。沿用自身实例身份,不增加授权;显示器标识变更仍使用 display.changed。隐藏不暂停模拟,作者可以按 visibility.changed 减少绘制;环境事件不承诺反映系统休眠/锁屏。
即时通知
notifications.show 需要声明和授予 notifications,标题1–48、正文1–200个UTF-16码元,拒绝NUL与控制字符(正文允许换行/制表符)。动作、通知身份、点击回调及主动撤下 notifications.dismiss 见桌面扩展。不支持HTML、图标路径、目标URL或定时参数。服务验证内容并保留额度;系统发送前检查文档与授权代次,系统操作不占用投递短锁。停用、卸载、撤销使尚未开始的发送失效,重新授权不复活旧请求。取消发送不能保证撤回已经交给系统的通知;迟到响应可以为CANCELLED,不代表外部副作用已回滚。
每宿主进程使用滚动 60 秒额度:每安装最多 3 次,全局最多 10 次,合法请求保留额度后即计数(系统拒绝也不自动重试);满额返回 QUOTA_EXCEEDED。使用即时、静音的系统Toast;系统抑制时不积压补发,成功不代表显示。系统通知不可用返回 RESOURCE_UNAVAILABLE。此接口的计时仍由当前文档负责,停用/退出后不执行;需要跨重载和重启保存时使用持久提醒,不向即时通知添加定时参数。
saves.context固定返回 {scope:"installation"},同一安装的所有实例自动共享存档,所有实例均可读写,提交冲突由作者处理,见存档协议。
collaboration.publish/get在同一安装内交换最新一条临时提示,所有实例均可发布/读取,见协作协议。
可信管理入口的全局暂停使用 WebView2 尽力挂起。组件可自行设计游戏内暂停,显隐、锁屏和挂起通知保持各自语义。
桌面扩展接口
授权目录写入、文件拖出、实例快捷键、系统音频回环、通知动作、附属窗口、绑定应用启动与组件凭据统一见桌面扩展。新增能力仍按安装共享授权,附属页面拥有独立文档会话,不产生额外持久实例。