跳到正文
豫言包管理
← 全部包

豫言操作系统网页文树 0.3.0

豫言 · 库

当前网页的受限文树构建、句柄管理与同源只读资源接口。

网页文树接口规范 0.3.0

这个能力组供在浏览器里运行的豫言应用构建当前页面。应用以 ID 获取已有节点,或按白册创建新节点,再写纯文本、受限属性并组合成树;也可以一次调用建出整棵树。句柄目前投影为字符串,语义上是当前应用实例内的资源编号;应用不得自行构造或跨实例保存。适配层必须拒绝未知标签、未知属性、可执行链接和无效句柄。

0.3.0 相对 0.2.0 只增函数与标签(旧函数签名与语义不变):选择器查询与节点读回(查询页面节点、读取页面节点标签/属性/文字/父级、查找最近页面祖先、页面节点匹配选择器)、子树导出(读取页面节点树)、替换页面节点、移除页面节点属性、设置页面节点超文本;标签白册增 em、i、kbd、sup、sub、blockquote、hr、dl、dt、dd(hr 是空元素)。它们让应用能对已经存在于页面里的服务端渲染内容(例如包阅读页)做后加工,而不必写 JavaScript;见[前端补全计划](../../豫言操作系统/文档/前端补全计划.汉语.md)。

0.2.0 相对 0.1.0:扩充标签与属性白册;新增一次建树(构建页面节点树)、插入/替换/移除/释放节点、追加文字、设置 iframe 地址、装入页面模板与读取页面网址;明确句柄的释放规则并提供自动释放。0.1.0 的函数签名全部不变。

句柄与释放规则

宿主为每个应用实例维护一张句柄表,上限 4096 项。下列函数会产生句柄:取得页面节点、新建页面节点、构建页面节点树,以及 0.3.0 的 查询页面节点(每个匹配元素一个)、读取页面节点父级、查找最近页面祖先。句柄表满时,产生句柄的函数抛事故(“页面节点句柄达到上限(4096)……”);因此应用必须遵守:

  1. 不再需要的句柄要用 `释放页面节点` 释放。 释放只是让应用放弃这个编号,不影响节点本身:节点若已在文档里,它仍在文档里。
  2. 同一个元素重复 取得页面节点 得到同一个句柄(不额外占位);释放后再取得会得到新的句柄编号。
  3. 自动释放(子树随根管理):移除页面节点、清空页面子节点、替换页面子节点 移除的子树里,所有节点的句柄(含被移除节点本身)由宿主同步释放,之后再使用这些句柄会抛“页面节点句柄无效”。装入页面模板 替换目标元素原有子节点时同理。
  4. 一次建树只占一个句柄:构建页面节点树 返回根节点一个句柄;树内其余节点没有句柄。若之后要单独操作树内某个节点,请在描述里给它一个 id 属性,再用 取得页面节点。
  5. 新建页面节点 与 构建页面节点树 造出的节点在被插入文档之前不属于文档;句柄被释放而节点从未插入,则节点随之被丢弃。

推荐用法:动态区域用 构建页面节点树 一次建出一行(一个句柄),插入页面子节点前 或 添加页面子节点 放进容器,立刻 释放页面节点;容器本身在启动时 取得页面节点 一次并持有;整个区域重绘时用 清空页面子节点 或 替换页面子节点,旧行的句柄随之自动释放。

失败语义

本组所有失败都属于违规调用或平台故障,一律抛豫言事故(可被应用捕获),事故消息为中文并保留原因:元素不存在、句柄无效或不是节点、标签或属性不在白册、链接不合规、层级违规(把祖先放进后代)、参照节点不是父节点的子节点、限额超出、句柄表满、网络与编码错误。宿主不得把失败当作成功,也不得让宿主异常越过豫言边界而使应用无法捕获。

白册

标签:a、span、small、b、strong、br、h1、h2、h3、h4、h5、p、section、div、article、nav、button、table、thead、tbody、tr、th、td、pre、code、time、ul、ol、li、label、select、option、form、input、textarea、details、summary,以及 0.3.0 增加的 em、i、kbd、sup、sub、blockquote、hr、dl、dt、dd。br、input、hr 是空元素,不能带文字或子节点。script、style、iframe、object、embed、link、meta、base、img、svg、video、dialog、template 等不在白册内。标签名区分大小写,只接受上列小写形式。

属性:与[网页界面接口规范](../网页界面/规范.汉语.md)“属性”一节是同一张表——class role type title hidden disabled readonly placeholder tabindex datetime maxlength rows colspan rowspan for value id checked selected open href target aria-* data-*;data-yy- 前缀保留给宿主;target=_blank 时宿主强制 rel=noopener noreferrer;href 只允许 /路径(不含 //)、#锚、https://、http://,http: 只对 a[href] 放行;style、src、srcdoc、name、action 与所有 on* 一律拒绝。

危险元素不给句柄:取得页面节点 对 script、style、iframe、object、embed、link、meta、base 等元素报错,避免应用改写它们的内容或属性。

函数

取得、创建、修改(0.1.0 起)

  • 取得页面节点 : 字符串(ID) → 字符串(句柄):按 ID 取当前页面里已有的节点。
  • 新建页面节点 : 字符串(标签) → 字符串(文字) → 字符串(类名) → 字符串(句柄):创建白册内的元素,文字为纯文本(空字符串表示不设),类名规则同 设置节点类名(空字符串表示不设)。文字至多 8 MiB。
  • 设置页面节点文字 : 字符串 → 字符串 → 有:整体设置节点的文字内容(textContent)。
  • 设置页面节点属性 : 字符串 → 字符串 → 字符串 → 有:按白册设置属性;布尔属性值用 ""、"true"、"false"。
  • 添加页面子节点 : 字符串(父) → 字符串(子) → 有:把子节点添加到父节点末尾;子节点已在文档中则是移动。
  • 清空页面子节点 : 字符串 → 有:移除节点的所有子节点,并释放被移除子树里所有节点的句柄(0.2.0 新增的释放规则)。
  • 设置页面标题 : 字符串 → 有、读取页面查询参数 : 字符串 → 字符串、读取页面资源文字 : 字符串 → 字符串:同 0.1.0。读取资源只接受以单斜杠开头的站内路径,不得含反斜杠;宿主核对解析后的 origin,禁止重定向,仅接受 HTTP 200,并逐块限制正文为 2 MiB,按严格 UTF-8 解码。

一次建树

构建页面节点树 : 字符串(描述 JSON) → 字符串(根节点句柄)。描述是 JSON 对象:

{"标签":"article","类":"event event-command","属性":{"data-yy":"打开:7","id":"行7"},
 "文字":"首段文字","子":[{"标签":"p","文字":"段落"},"裸文字",{"标签":"ul","子":[{"标签":"li","文字":"一"}]}]}
  • 标签 必填,须在白册内;类、属性、文字、子 可省。属性 的值可以是文字、数字或布尔(数字转十进制文字,布尔转 true/false)。
  • 文字 是节点的第一个子节点(文本节点);子 里的项可以是对象(子元素)或字符串(文本节点)。
  • 未知字段、类型不对、空元素带文字或子节点、id 在树内重复或与文档已有 ID 冲突,都报错并指出出错位置(如 根.子[0].子[1])。
  • 限额:深度至多 32(根为第 1 层)、节点(元素与文本节点)至多 2000、文字总量至多 1 MiB(UTF-8 字节)、属性名值总字符数至多 1 MiB、描述 JSON 至多 2 MiB(UTF-16 码元)。
  • 树是整体原子地建出:任何一步失败都不留下任何节点与句柄。建好的树在被插入文档之前不属于文档。

组合

  • 插入页面子节点前 : 字符串(父) → 字符串(子) → 字符串(参照) → 有:把子节点插入父节点中参照子节点之前;参照为空字符串表示追加到末尾;参照必须是父节点的子节点;子节点已在文档中是移动。
  • 替换页面子节点 : 字符串(父) → (列于 字符串)(子们) → 有:用给定的子节点列表整体替换父节点的所有子节点(至多 2000 项,不得重复);被替换掉的子树里所有句柄同时释放(仍在新列表里的节点不受影响);空列表等于清空。
  • 移除页面节点 : 字符串 → 有:把节点从其父节点移除,并释放该节点及其子树内所有节点的句柄。
  • 释放页面节点 : 字符串 → 有:释放一个句柄;句柄无效时抛事故。
  • 追加页面节点文字 : 字符串 → 字符串 → 有:在节点末尾追加纯文本,用于流式输出。末尾已是文本节点时并入它(连续追加不产生碎片节点),否则新建文本节点。单次至多 8 MiB,追加后该文本节点至多 8 MiB;空元素报错。

框架与模板

  • 设置页面框架地址 : 字符串(iframe 的 ID) → 字符串(网址) → 有:改页面里已有 iframe 的 src。iframe 必须由页面(HTML)声明 sandbox 属性——应用不能创建 iframe,也不能修改 sandbox、allow、referrerpolicy 等属性(不在白册内)。地址只允许:以 / 开头但不以 // 开头的同源路径,或 `https://` 网址,且其 origin 是页面在该 iframe 的 `data-yy-frame-origins` 属性(空格分隔的 origin 列表)里声明的(与页面同源的 https 网址亦可)。含空白、控制字符、反斜杠或超过 2048 个字符的地址拒绝;空字符串表示移除 src。
  • 装入页面模板 : 字符串(资源路径) → 字符串(模板内元素 ID) → 字符串(目标 ID) → 有:用与 读取页面资源文字 相同的规则读取同源 HTML 模板,在模板里按 ID 选出一个元素,复制后清洗,再作为目标元素的唯一子节点装入(目标原有子节点被移除,其子树内的句柄同时释放)。清洗:剔除 script、style、iframe、object、embed、link、meta、base、frame、frameset、applet、template 元素,剔除所有 on*、srcdoc、formaction 属性,剔除 javascript: 与 vbscript: 链接,target=_blank 的链接强制 rel=noopener noreferrer;模板里没有该 ID 时抛事故。此函数会挂起等待网络。
  • 读取页面网址 : 有 → 字符串:当前页面的完整 URL(含查询与片段),用于构造给其他能力包使用的绝对网址。

选择器查询与节点读回(0.3.0)

这一组让应用读到页面里已有的内容(包括没有 ID 的元素)并加以改造。所有“选择器”参数是 CSS 选择器(选择器列表亦可,至多 512 个字符),语法错误抛事故;所有“节点”参数是上文的句柄,必须是元素。

  • 查询页面节点 : 字符串(根) → 字符串(选择器) → 字符串(JSON 数组):等价于 querySelectorAll。根 是节点句柄,空字符串表示整个文档;根是 <template> 元素时在它的 content 里查询(用来读页面里放着的数据模板)。返回 JSON 数组文字,每项是一个元素句柄字符串,按文档顺序,同一元素得同一句柄。危险元素(script、style、iframe 等)不出现在结果里;匹配超过 1024 个元素、或句柄表剩余不足时抛事故(应先收窄选择器、并及时释放句柄)。
  • 读取页面节点标签 : 字符串 → 字符串:小写标签名。
  • 读取页面节点属性 : 字符串(节点) → 字符串(名称) → (爻 合 字符串):属性的原样文字,返回(存在与否,值);名称与限制同网页界面包的 读取节点属性。
  • 读取页面节点文字 : 字符串 → 字符串:textContent,按 UTF-8 至多 8 MiB。
  • 读取页面节点父级 : 字符串 → 字符串:父元素的句柄;没有父元素(含节点在 <template> 内容里)或父是危险元素时返回空字符串。
  • 查找最近页面祖先 : 字符串(节点) → 字符串(选择器) → 字符串:等价于 closest(含自身),返回句柄,没有则空字符串。
  • 页面节点匹配选择器 : 字符串 → 字符串 → 爻:等价于 matches。
  • 读取页面节点树 : 字符串(节点) → 整数(深度上限) → 字符串(JSON):把元素及其子树导出为描述文字,形状与 `构建页面节点树` 的输入相同——{"标签","类","属性","子"},文字节点是 子 里的字符串,节点之间的空白文字照原样保留;只导出白册内的属性(class 写在 类 里,on*、style、src 等被略去);危险元素与 <template> 子树被略去;深度上限 1 至 32,超过所给上限、节点超过 2000 或文字超过 1 MiB 时抛事故。导出的描述再用 构建页面节点树 重建时仍按白册校验:标签不在白册、id 与文档中已有的重复、链接不合规都会被拒,应用需要先改写(例如去掉 id)。典型用法:导出说明区的内容,在豫言里按文字偏移切分、改写后重建,再 替换页面子节点。
  • 替换页面节点 : 字符串(旧) → 字符串(新) → 有:旧.replaceWith(新);新 可以是新建节点或已在文档里的节点(则是移动)。旧节点及其子树的句柄不释放,旧节点可以再放到别处——例如先把它替换成一个新建的链接,再 添加页面子节点(链接, 旧) 完成“给原节点包一层”。旧节点没有父节点、或把祖先放进后代时抛事故。
  • 移除页面节点属性 : 字符串 → 字符串 → 有:规则同网页界面包的 移除节点属性。
  • 设置页面节点超文本 : 字符串(节点) → 字符串(HTML) → 有:把 HTML 文字在惰性环境里解析,按白册清洗并复制后整体替换节点的子节点。标签与属性必须全在白册内,不合规时抛事故并指出位置,不悄悄剔除;注释被丢弃;文字至多 2 MiB、节点至多 5000、深度至多 32;被替换掉的旧子树里的句柄同时释放。用于装入豫言高亮器等可信来源产生的只含 <span class> 的片段;它不是通用的 innerHTML。

限额

| 项 | 限额 | | --- | --- | | 句柄表 | 每个应用实例 4096 项 | | 一次建树 | 深度 ≤ 32;节点 ≤ 2000;文字 ≤ 1 MiB(UTF-8);属性总量 ≤ 1 MiB;描述 ≤ 2 MiB | | 替换页面子节点 | ≤ 2000 项 | | 查询页面节点 | 选择器 ≤ 512 字符;匹配 ≤ 1024 个元素 | | 读取页面节点树 | 深度 ≤ 32;节点 ≤ 2000;文字 ≤ 1 MiB | | 设置页面节点超文本 | HTML ≤ 2 MiB;节点 ≤ 5000;深度 ≤ 32 | | 文字与追加 | 单次与单个文本节点 ≤ 8 MiB | | 属性值 | ≤ 65536 个 UTF-16 码元 | | 页面资源 | ≤ 2 MiB,仅同源 GET |

跨宿主一致性

  • 白册、限额、释放规则与失败分类必须与本文一致;宿主可以更严,不可以更松。
  • 句柄编号是不透明字符串,应用只能原样传回;宿主不得让已释放的句柄编号悄悄指向另一个节点(参考实现的编号在实例内单调递增、绝不复用)。
  • 构建页面节点树 在树内的 id 唯一性检查只在一次调用范围内;建好后插入文档时若与既有 ID 重复,后者行为按浏览器(getElementById 取文档顺序里的第一个),应用应保证 ID 唯一。

一致性样例

  1. 建树 {"标签":"div","子":[{"标签":"script"}]}:抛事故,指出位置 根.子[0],句柄表不增。
  2. 建一棵 2 层的树并 添加页面子节点 到容器,再 释放页面节点(根):节点仍在文档中,句柄表回到原来的数量;循环 10000 次后句柄表仍为原数量。
  3. 容器里有 3 行,各行及其子节点都拿过句柄;清空页面子节点(容器) 之后,这些句柄全部失效,容器的句柄仍有效。
  4. 设置页面框架地址("网站画面", "https://evil.example/"):iframe 的 data-yy-frame-origins 里没有该 origin,抛事故。
  5. 装入页面模板 装入的模板里含 <script> 与 onclick:装入后文档里没有脚本元素,元素上没有 onclick。
  6. (0.3.0)页面有三个 <a class="引用" href="/x">:查询页面节点("", ".引用") 返回三个句柄;读取页面节点属性(柄, "href") 为(阳,“/x”);读取页面节点父级(柄) 是它们共同的父元素句柄;查找最近页面祖先(柄, "section") 找到外层 section。
  7. (0.3.0)<template id="资料"><a data-qualified="甲.乙" href="/a">甲</a></template>:查询页面节点(取得页面节点("资料"), "a[data-qualified]") 得到模板内容里的那个 a(普通的文档查询看不到它)。
  8. (0.3.0)<code>ab<b class="k">cd</b></code> 导出为 {"标签":"code","子":["ab",{"标签":"b","类":"k","子":["cd"]}]};把它原样交给 构建页面节点树 得到等价的新树。
  9. (0.3.0)替换页面节点(旧, 新链接) 之后 添加页面子节点(新链接, 旧):旧节点仍有效,最终位置在新链接内、新链接在原处。
  10. (0.3.0)设置页面节点超文本(柄, "<span class=\"tok-k\">甲</span>乙") 成功;含 <script>、onclick 或 style 的 HTML 抛事故,节点内容不变。

边界

本包不规定:选择器查询之外的节点遍历(nextSibling、TreeWalker、Range 等:请用 读取页面节点树 在豫言里遍历)、任意 CSS 与样式对象、img 与 svg、动态创建 iframe、自定义元素、Shadow DOM、MutationObserver、文档片段。没有文树的平台不提供这个包。

依赖

查看包配置

上传历史

当前上传修订:1 ·

  1. 上传修订 1
    下载 ZIP

包层级

父包:豫言操作系统接口

没有子包。