加载图片与字体
实现 assetResolver——外部资源进入分页文档的唯一通道。
在 HTML 里加一个 <img>,它不会加载。这不是缺陷——这正是 Imposia 围绕其构建的边界,而 assetResolver 是你有意打开这道边界的方式。
为什么默认什么都不加载
承载页面的 iframe 处于沙箱中,并施加严格的内容安全策略。connect-src 为 'none',图片、字体和媒体只能来自 Core 自己创建的 blob: URL。作者书写的 URL 永远不会变成网络请求。
这种方式带来什么
不受信任的标记无法让这个框架访问网络。到达页面的每一个字节都先经过你的应用,因此由你决定允许什么、来自哪里,以及附带哪些凭据(如果有的话)。
Imposia 在你的 HTML 和 CSS 中发现资源,为每一个资源询问你的解析器,把获准的字节变成 Core 拥有的 blob: URL,并在文档被替换、失败或销毁时撤销它们。
实现解析器
解析器就是一个异步函数。它接收请求,用字节或拒绝作答。
import type { AssetResolver } from "@imposia/core";
const ALLOWED = new Set(["https://cdn.example.com"]);
export const assetResolver: AssetResolver = async ({ url, kind, baseUrl, signal }) => {
const target = new URL(url, baseUrl);
if (!ALLOWED.has(target.origin)) {
return { status: "blocked", reason: `Origin not allowed: ${target.origin}` };
}
const response = await fetch(target, { signal });
if (!response.ok) {
return { status: "blocked", reason: `HTTP ${response.status}` };
}
return {
status: "resolved",
bytes: new Uint8Array(await response.arrayBuffer()),
mimeType: response.headers.get("content-type") ?? "application/octet-stream",
};
};挂载文档时传入一次:
<ImposiaPageViewer
source={{ html, baseUrl: "https://cdn.example.com/docs/" }}
documentOptions={{ assetResolver }}
/>请求
| 字段 | 含义 |
|---|---|
url | 与作者书写完全一致的 URL,可能是相对路径 |
kind | image、font、media 或 stylesheet |
baseUrl | 文档的基准 URL;仅在 source 上提供过时存在 |
signal | 该世代被取代、失败或销毁时中止 |
请始终自己用 baseUrl 解析 url,并始终把 signal 转发给 fetch。忽略 signal 的解析器会继续为一个没有人会看到的世代工作。
应答
返回 { status: "resolved", bytes, mimeType } 放行资源,或返回 { status: "blocked", reason } 拒绝它。拒绝是正常结果,不是错误:文档照常提交,Imposia 会发出 RESOURCE_BLOCKED 警告并指明被拒的资源。
返回之后仍会被拒的情况
批准字节并不是最终裁定。Core 会校验它们,不匹配按拒绝处理。
| 检查 | 规则 |
|---|---|
| MIME 白名单 | 图片必须是 PNG、JPEG、GIF、WebP 或 AVIF;字体为 WOFF、WOFF2、TTF 或 OTF;样式表为 text/css |
| 魔数 | 字体容器按真实签名(wOFF、wOF2、OTTO 等)校验,而不是按声明的类型 |
| 上限 | 引用数量、总字节数和嵌套深度都有上限 |
这就是为什么对真实 PNG 返回 application/octet-stream 的解析器会看到图片被拦下:声明的类型必须正确。
重复引用仍计入字节上限
在同一个世代内,指向相同 URL 的多个引用共享一次解析器调用和一个 blob: URL。字节统计有意不做去重——每次出现都计入 maxAssetBytes,因此这个上限的含义与该优化出现之前完全相同。
样式表会带来更多工作
当你放行一个样式表时,Imposia 会解析它,并发现它引用的资源——@font-face 来源、url() 值和嵌套的 @import 规则。每一个都会作为独立请求回到你的解析器,baseUrl 设为该样式表所在位置。深度有上限,@import 链不会无限递归。