tuffite0.1
契约与细节

Explorer API

嵌入、导航、通信和转移隔离的浏览器表面。

实验性源码版本。生产安全加固与 SDK 发布仍在进行中。

元素与权限#

Explorer 是原生 DOM 元素 HTMLExplorerElement,而非 Rust 方法或 tuffite.explorer ShellAPI 命名空间。Framework 需要启用 TuffiteExplorer。在 app.security.capabilities 中为所属文档 origin 授予 explorer.create;隔离脚本执行还需要 explorer.executeJavaScript。嵌入页面执行原生调用或创建嵌套 Explorer 时需要自己的授权。

html
<explorer id="browser" src="https://example.com/" navigation="emit" dialogs="emit"></explorer>

属性与创建策略#

  • src 请求导航;load(url) 显式启动页面。contextMenuEnabled 切换原生菜单,默认为 false。
  • navigation="emit" 拦截渲染器当前页导航;"allow" 在表面内导航。dialogs="emit" 转发对话框;"suppress" 拒绝它们。
  • partition、navigation 和 dialogs 在页面创建时固定。修改策略需重建元素。deferred 延迟初次创建;load() 可启动延迟创建的元素。

事件与对话框#

  • error:创建或挂接失败。loadstart、loadstop、loadcommit:导航状态。loaderror 还提供 errorCode 和 errorDescription。
  • navigation、open、popup 和 newwindow 在 event.detail 中携带 {url, disposition, userGesture, from},分别表示拦截导航、普通 window.open()、弹出窗口及其他新窗口请求。
  • 请求事件和 dialog 跨 Explorer 边界冒泡至 window.tuffite。stopPropagation() 同时停止本地冒泡和跨表面转发。
  • dialog 的 detail 是 ExplorerDialog,包含 type、message、defaultValue、isReload、from、accept(promptText) 和 dismiss()。控制器只能应答一次;失效或重复应答返回 false。应答不会停止事件传播。
  • detached 与 attached 报告归属变更;ExplorerTransfer 发出 statechange。

Explorer 树通信#

element.postMessage(data) 发给直接子页面。window.tuffite.postMessage(data, {to}) 接受 parent、top(默认)或 event.detail.from 中浏览器签发的引用。消息使用 JSON,浏览器检查归属和树成员关系。Promise<boolean> 表示接受并投递或入队,不表示应用应答。ShellAPI invoke 和 channel 事件使用独立二进制传输。

javascript
await explorer.postMessage({ type: 'refresh' });
await window.tuffite.postMessage({ type: 'ready' }, { to: 'parent' });
window.tuffite.addEventListener('message', ({ detail }) => {
  console.log(detail.data, detail.from);
});

存储分区#

  • 省略 partition 共享直接父页面的实际存储后端,包括内存后端;"shared" 不是有效属性值。
  • persist:name 使用命名磁盘分区;memory:name 使用命名内存分区。
  • inherit+persist:name 和 inherit+memory:name 仅在目标 cookies 与 localStorage 均为空时复制父页面数据。这是快照,不是实时同步;不复制其他存储类型。
  • 每个 Explorer 保持独立渲染器隔离域,即使共享存储也是如此。常规 origin 规则仍然适用。

转移运行中的页面#

准备已连接且无 src、无现有页面的接收元素。detach() 返回 ExplorerTransfer,具有一次性 token 和 pending、attaching、transferred、closed 状态。成功 attach() 会消耗 token。废弃转移需调用 close();attaching 时关闭会拒绝。源文档导航或销毁释放待转移页面。普通同文档 moveBefore() 保留现有页面,无需转移。

javascript
const target = document.createElement('explorer');
target.deferred = true;
target.navigation = source.navigation;
target.dialogs = source.dialogs;
if (source.partition) target.partition = source.partition;
container.append(target);
const transfer = await source.detach();
try {
  await target.attach(transfer.token);
  source.remove();
} catch (error) {
  await source.attach(transfer.token);
  throw error;
}

权威 WebIDL 接口#

以下声明直接从 Framework 的 Explorer WebIDL 生成,包含辅助选项字典。可在 Playground Capability Lab 中验证导航、通信、存储、对话框和转移。

ExplorerDialog

webidl
[Exposed=Window, RuntimeEnabled=TuffiteExplorer] interface ExplorerDialog {
    readonly attribute DOMString type;
    readonly attribute DOMString message;
    readonly attribute DOMString defaultValue;
    readonly attribute boolean isReload;
    readonly attribute DOMString from;
    [CallWith=ScriptState] Promise<boolean> accept(optional DOMString promptText = "");
    [CallWith=ScriptState] Promise<boolean> dismiss();
};
查看源码

ExplorerExecuteJavaScriptOptions

webidl
dictionary ExplorerExecuteJavaScriptOptions {
    DOMString world = "isolated";
    DOMString expectedDocumentId = "";
};
查看源码

ExplorerReloadOptions

webidl
dictionary ExplorerReloadOptions {
    boolean bypassCache = false;
};
查看源码

ExplorerTransfer

webidl
[Exposed=Window, RuntimeEnabled=TuffiteExplorer] interface ExplorerTransfer : EventTarget {
    readonly attribute DOMString token;
    readonly attribute DOMString state;
    [CallWith=ScriptState] Promise<undefined> close();
    attribute EventHandler onstatechange;
};
查看源码

HTMLExplorerElement

webidl
[Exposed=Window, RuntimeEnabled=TuffiteExplorer] interface HTMLExplorerElement : HTMLElement {
    [CEReactions, Reflect, URL] attribute USVString src;
    [CEReactions] attribute DOMString partition;
    [CEReactions] attribute DOMString navigation;
    [CEReactions] attribute DOMString dialogs;
    attribute boolean contextMenuEnabled;
    [CEReactions] attribute boolean deferred;
    [CallWith=ScriptState] Promise<ExplorerTransfer> detach();
    [CallWith=ScriptState] Promise<undefined> attach(DOMString token);
    attribute EventHandler ondetached;
    attribute EventHandler onattached;
    void load(USVString url);
    [CallWith=ScriptState] Promise<boolean> cancelDownload(unsigned long id);
    [CallWith=ScriptState] Promise<boolean> download(USVString url);
    [CallWith=ScriptState] Promise<any> getDownloads();
    [CallWith=ScriptState] Promise<DOMString> readDownload(unsigned long id);
    [CallWith=ScriptState] Promise<DOMString> capturePreview();
    [CallWith=ScriptState] Promise<boolean> back();
    [CallWith=ScriptState] Promise<boolean> forward();
    [CallWith=ScriptState] Promise<boolean> go(long offset);
    [CallWith=ScriptState] Promise<boolean> reload(optional ExplorerReloadOptions options = {});
    [CallWith=ScriptState] Promise<boolean> stop();
    [CallWith=ScriptState] Promise<any> getNavigationState();
    [CallWith=ScriptState] Promise<any> executeJavaScript(
        DOMString source,
        optional ExplorerExecuteJavaScriptOptions options = {});
    [CallWith=ScriptState] Promise<boolean> postMessage(any data);
};
查看源码