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

豫言操作系统网页事件 0.2.0

豫言 · 库

统一等待网页事件、订阅界面事件(含选择器过滤与目标属性载荷)及读取队列丢弃数的公共接口。

网页事件接口规范 0.2.0

本包为浏览器(及能提供同义事件队列的宿主)里的豫言应用提供统一等待点:界面事件、页面消息、定时、事件流、请求、编译、可见性、联机、历史和关闭都进入同一个先进先出队列,应用用一个阻塞调用 等待网页事件 取下一个事件,再自行分派。另外提供 订阅界面事件:由应用在订阅时预先声明“同步决定”(阻止默认动作、停止传播、按键规则),宿主在原生回调里当场执行,因为豫言只能事后处理事件,无法改变本次事件的默认行为。应用依赖本包后,全部函数都必须实现,缺少任何一个函数时不得装载。

0.2.0 相对 0.1.0 只增不减(六个函数的签名不变):订阅界面事件 的策略 JSON 新增三个字段——选择器(只对匹配某个 CSS 选择器的元素产生事件并执行阻止默认等同步动作)、带属性(事件载荷里带匹配元素的若干属性)、带矩形(载荷里带匹配元素的位置矩形);事件载荷相应新增 目标标识、属性、矩形 三个字段。同时把“定时”“事件流”“请求”“编译”四类事件明确指给各自的接口包:[网页定时](../网页定时/规范.汉语.md)、[网页事件源](../网页事件源/规范.汉语.md)、[网页请求](../网页请求/规范.汉语.md)、[网页编译](../网页编译/规范.汉语.md)。

事件队列

宿主为每个应用实例维护一个队列。每个事件有类型,队列内按到达顺序排列,宿主给每个事件一个单调递增的序号(从 1 起)。类型取下列固定集合:

| 类型 | 含义 | 来源 | | --- | --- | --- | | 界面 | 点击、输入、变更、提交、键盘、组字、焦点、滚动、对话框 cancel/close 等 DOM 事件 | 本包 订阅界面事件;网页界面包的旧式隐式监听 | | 消息 | 当前文档里以“豫言”开头的应用消息 | 网页消息包 | | 定时 | 一次或周期定时到期 | 网页定时包 | | 事件流 | 服务器推送(SSE)事件:打开、成批的消息、错误 | 网页事件源包 | | 请求 | 非阻塞网页请求的完成或失败 | 网页请求包 | | 编译 | 本地编译的进度(成批)与结果 | 网页编译包 | | 可见性 | 页面可见性变化(visibilitychange) | 本包 订阅界面事件 | | 联机 | 联机状态变化(online、offline) | 本包 订阅界面事件 | | 历史 | 历史遍历(popstate、hashchange) | 本包 订阅界面事件 | | 关闭 | 宿主关闭队列 | 宿主 | | 宿主 | 低层宿主库的原始事件(动画帧、空闲回调、工作线程、广播频道、套接字、公开操作、文树变动等) | 宿主库;应用一般不使用 |

事件流、请求、编译、定时四类事件的字段与产生时机由各自的包规定,本包只规定它们共用的队列、序号、背压与取法。

背压与丢弃

  • 每个类型有独立的队列上限,默认 1024,可用 设置网页事件队列上限 调整为 1 至 65536;宿主创建时也可传入初始上限。
  • 某类型的队列满时,新事件到达会丢弃该类型最旧的事件,并计数;别的类型不受影响。读取网页事件丢弃数 返回自宿主启动以来的丢弃总数,读取网页事件类型丢弃数 返回某个类型的丢弃数。被丢事件留下的序号缺口可供应用发现丢失并重新同步。
  • 宿主 类型例外:为免丢失待答的公开操作调用,它满时拒绝新事件并报错,不丢旧事件(保持旧宿主库的行为)。
  • 合并:高频事件(scroll、resize、mousemove、pointermove、touchmove、wheel、dragover、selectionchange)默认合并——同一订阅、同一目标标识的事件若紧接在队列尾部,则新事件就地替换旧事件,不占新位置,也不算丢弃。订阅策略里的 合并 可覆盖默认。
  • 批事件:事件流与编译进度由宿主合成批事件(详情 里带 批 数组,见网页事件源、网页编译包),并配合背压保证不丢失;界面与消息事件不合批。

与旧函数的关系

网页界面包的 等待界面事件 和网页消息包的 等待网页消息 保留,但改为按类型从同一队列取事件:前者只取 界面 类型,后者只取 消息 类型,其余类型的事件原样留在队列里,不被吞掉。同一应用可以混用 等待网页事件 与这两个旧函数;混用时同一个事件只会被取走一次,先调用者先得。底层宿主库的 等待浏览器事件(原始 JSON)仍取任意类型的下一个事件,仅供旧应用继续运行,新应用不应与本包混用。

函数

等待网页事件 : 有 → 字符串

阻塞直到队列里有事件,返回下一个事件(不分类型,按到达顺序)的 JSON 文字。宿主关闭后,每次调用都返回 {"类型":"关闭","订阅号":0,"名称":"关闭"}。调用可挂起,应用不得由此取得原生 DOM 对象。

事件 JSON 是对象,含下列字段;各类事件只填相关字段,其余省略:

| 字段 | 类型 | 含义 | | --- | --- | --- | | 类型 | 文字 | 上表之一,恒有 | | 序 | 整数 | 宿主分配的序号,恒有(关闭事件除外) | | 订阅号 | 整数 | 产生事件的订阅号;旧式隐式监听与宿主自身产生的事件为 0,定时事件为定时号 | | 标识 | 文字 | 触发元素的 ID,无 ID 时为空文字(界面类型恒有) | | 名称 | 文字 | 事件名(click、keydown、visibilitychange……;消息为消息名;定时为“定时”;关闭为“关闭”) | | 操作键 | 文字 | 触发元素及其祖先(止于订阅目标,含目标自身)中最近的 data-yy 属性值;没有则省略 | | 目标标识 | 文字 | 仅当订阅策略带 选择器 时出现:原始事件目标的 ID,无 ID 时为空文字(此时 标识 是匹配元素的 ID) | | 属性 | 对象 | 仅当订阅策略带 带属性 时出现:匹配元素上存在的、被请求的属性(名称→文字);不存在的属性省略,值超过 4096 个 UTF-16 码元的省略 | | 矩形 | 对象 | 仅当订阅策略带 带矩形 时出现:匹配元素的 getBoundingClientRect(),取整后的 {"左":整数,"上":整数,"右":整数,"下":整数}(视口坐标) | | 值 | 文字 | 目标是 input、textarea、select 时的当前值;值长于 65536 个 UTF-16 码元时省略并带 "值过长":true(改用网页界面包的 读取节点值);type=password 的输入框、scroll、mousemove 等无值事件不带 | | 选中 | 爻 | 目标是 checkbox 或 radio 时的 checked(旧式隐式监听的事件恒带 值 与 选中) | | 键 代码 | 文字 | 键盘事件的 key 与 code | | ctrl meta alt shift | 爻 | 键盘与鼠标事件的修饰键 | | 重复 | 爻 | 键盘事件的 repeat | | 组字中 | 爻 | 键盘事件的 isComposing;input 事件的 isComposing;compositionstart、compositionupdate 为真,compositionend 为假 | | 按钮 | 整数 | 鼠标事件的 button | | 已阻止默认 | 爻 | 宿主在原生回调里是否已对该事件调用 preventDefault(界面类型恒有) | | 可取消 | 爻 | 仅当订阅要求阻止默认而浏览器不允许取消该事件时出现,恒为假 | | 详情 | 任意 JSON | 见下表 | | 详情错误 | 文字 | 仅消息类型:详情超过 1 MiB 或不是有效 JSON 时,详情 为 null 并带此字段 |

详情 的形状:

| 事件 | 详情 | | --- | --- | | input | {"输入类型":inputType,"数据":data 或 null} | | compositionstart/update/end | {"数据":data} | | focus blur focusin focusout | {"关联标识":relatedTarget 的 ID 或空文字} | | scroll | {"顶":整数,"总高":整数,"可视高":整数}(目标是文档时取滚动元素) | | dialog 的 cancel close | {"返回值":returnValue} | | 可见性 | {"可见":爻} | | 联机 | {"联机":爻} | | 历史 popstate | {"网址":当前地址,"状态":JSON 化的 state 或 null}(state 不能 JSON 化或超过 64 KiB 时为 null) | | 历史 hashchange | {"旧网址":文字,"新网址":文字} | | 消息 | 消息的 JSON 详情 | | 定时 | {"种类":"一次"或"重复","标记":文字,"时刻":整数} | | 宿主 | 原始事件对象 |

订阅界面事件 : 字符串 → 字符串 → 字符串 → 整数

参数依次为目标、事件名、策略 JSON,返回订阅号(正整数)。

  • 目标:页面里已有元素的 ID,或保留字 文档、窗口(因此 ID 为“文档”或“窗口”的元素不能作为目标)。目标元素须在订阅时存在;元素以后被替换掉,订阅随之失效。
  • 事件名:ASCII,[A-Za-z][A-Za-z0-9_.:-]*,至多 64 个字符。visibilitychange 产生可见性类型事件,online、offline 产生联机类型,popstate、hashchange 产生历史类型,其余产生界面类型。以“豫言”开头的消息名不能在这里订阅,应使用网页消息包。
  • 委托:目标下任意后代触发的同名事件都会产生事件,标识 为触发元素的 ID(没有则为空文字),操作键 为最近带 data-yy 的祖先(含自身)的值。因此动态生成的按钮不必有 ID,只要带 data-yy。不冒泡的事件(focus、blur、scroll、close、cancel、load、error、toggle、invalid、mouseenter、mouseleave、pointerenter、pointerleave)宿主一律在捕获阶段监听,同样可以委托。
  • 同一目标、同一事件名可以订阅多次,每次得到不同的订阅号,各自产生事件。
  • 订阅总数至多 256。目标不存在、事件名或策略非法、数量超限时抛豫言事故(可被捕获);失败的订阅不留下任何痕迹。

策略 JSON是对象,字段全部可省略;出现未知字段或类型不对一律报错,避免笔误被默默忽略:

| 字段 | 类型 | 含义 | | --- | --- | --- | | 阻止默认 | 爻 | 在原生回调里调用 preventDefault()(事件可取消时);不能与 被动 同时为真 | | 停止传播 | 爻 | 调用 stopPropagation() | | 停止同处 | 爻 | 调用 stopImmediatePropagation()(同目标的后续监听器也收不到) | | 捕获 | 爻 | 在捕获阶段监听(不冒泡事件总是捕获) | | 被动 | 爻 | 被动监听;scroll、wheel、touchstart、touchmove 在没有上面三个动作时默认被动 | | 合并 | 爻 | 是否合并紧邻的同源事件,默认仅高频事件为真 | | 一次 | 爻 | 首个事件之后自动取消订阅(该事件仍会送达) | | 仅命中 | 爻 | 有键规则时只把命中的事件送入队列 | | 键规则 | 数组 | 至多 32 条,见下;仅适用于 keydown、keyup、keypress | | 选择器 | 文字 | CSS 选择器(选择器列表亦可),至多 512 个字符,订阅时校验,非法则报错;见下文“选择器过滤” | | 排除选择器 | 文字 | CSS 选择器,至多 512 个字符,订阅时校验;原始事件目标(含自身)落在匹配它的元素之内时,本次事件被忽略:既不投递,也不执行任何同步动作;见下文“选择器过滤” | | 带属性 | 数组 | 至多 8 个属性名([a-z][a-z0-9:_-]*,至多 64 字符,不得以 on 开头,不得是 style、srcdoc),不得重复;事件载荷带 属性 | | 带矩形 | 爻 | 为真时事件载荷带 矩形 |

没有键规则时,阻止默认、停止传播、停止同处 对该订阅的每个事件生效。有键规则时,动作只在某条规则命中时才执行:若三个动作字段一个都没给,命中即 阻止默认(沿用宿主库“键盘阻止默认”规则的含义);若给了其中任何一个,则只执行所给的动作。

键规则是对象,同一条规则内各条件取“且”,多条规则取“或”;字段:

| 字段 | 含义 | | --- | --- | | 键 | 与 event.key 相同;单个字母不分大小写(l 也匹配 L)。键 与 代码 至少给一个 | | 代码 | 与 event.code 相同 | | ctrl meta alt shift | 布尔;省略表示不参与匹配,给出则必须与事件相同 | | 含组字 | 布尔;默认假:输入法组字期间(isComposing 或 keyCode 为 229)的按键不命中,以免破坏输入法;为真则组字期间也匹配 | | 仅选区为空 | 布尔;为真时,若输入框内或页面上有选中文字则不命中(例如只在没有选区时拦截 Ctrl+C) | | 仅目标标识 | 文字;事件目标须是该 ID 的元素本身或其后代 | | 仅当属性 | 对象 {"标识":ID,"属性":名,"值":文字或 null}:该元素的属性须等于给定值;值 为 null 表示该属性不存在;hidden、disabled、readonly、open、checked、selected 等布尔属性用 "true"(存在)与 "false"(不存在)表示 |

规范强调:所有同步条件都写在订阅时;键规则、阻止默认与停止传播在宿主的原生事件回调里当场判定和执行,豫言收到事件时本次事件的默认行为早已决定,事后无法改变。要按运行时状态改变拦截行为,应用应取消并重新订阅,或使用 仅当属性,让宿主读取页面里的属性现值。

浏览器的实际约束:dialog 的 cancel(Esc)只有在页面已获用户激活时才允许取消;否则事件不可取消,preventDefault 无效,此时事件载荷带 可取消:false、已阻止默认:false,dialog 照常关闭。

选择器过滤(0.2.0):策略带 选择器 时,宿主在原生回调里先做“委托匹配”,再谈其余:

  1. 取事件的原始目标(若是文本节点则取其父元素);目标不是元素(例如 scroll 的目标是文档)则不匹配。
  2. 取“最近匹配祖先”m = 从原始目标起向上(含自身)第一个匹配选择器的元素,且必须在订阅边界之内(订阅目标是元素时,m 须是该元素或其后代;订阅目标是 文档 或 窗口 时,不限)。
  3. 没有 `m`:本次事件既不投递,也不执行阻止默认、停止传播、停止同处——原生事件不受这次订阅的影响。
  4. 有 m:照常检查键规则并执行动作;事件载荷里所有关于“目标元素”的字段——标识、操作键(从 m 向上找 data-yy,止于订阅边界)、值、选中、属性、矩形——都取 m(focus 类事件详情里的 关联标识 仍是 relatedTarget 的 ID,不受影响);原始目标的 ID 由 目标标识 报告。

典型用法:订阅界面事件("文档", "click", {"捕获":true,"选择器":".豫菜单, .豫导航 [data-lang], a[href]","阻止默认":true,"停止同处":true,"带属性":["href","data-lang"]})——只拦截导航链接与语言按钮的点击并读到被点链接的 href,页面上其他点击丝毫不受影响;订阅界面事件("文档", "mouseover", {"选择器":".类型引用[title]","带属性":["title"],"带矩形":true})——悬停在带标题的引用上才产生事件,并带回标题与位置,用于摆放浮层。选择器的语法错误在订阅时报错;选择器 与 键规则 可以并用(先选择器,后键规则)。

排除选择器(0.2.0):策略带 排除选择器 时,先检查原始目标(文本节点取其父元素):若它或它的某个祖先匹配该选择器(closest,不受订阅边界限制),本次事件被忽略,然后才做上面的选择器匹配。用来表达“点在某区域之外”——例如收起下拉面板:订阅界面事件("文档", "click", {"排除选择器":".文件目录"}) 只在点击落在 .文件目录 之外时产生事件。(选择器 用的是“最近匹配祖先”,无法表达“之外”::not(...) 会向上匹配到 body。)

没有 选择器 时,带属性、带矩形 作用于原始目标本身(目标是元素时)。带属性 读取的是属性(getAttribute,原样文字)而不是 DOM 属性;type=password 的输入框上名为 value 的属性不带出。

取消订阅界面事件 : 整数 → 有

取消订阅并移除该订阅尚未取走的事件。订阅号无效或已取消时什么也不做。已经被 等待网页事件 取走的事件不受影响。

读取网页事件丢弃数 : 有 → 整数、读取网页事件类型丢弃数 : 字符串 → 整数

见“背压与丢弃”。类型名不属于上表时,后者返回 0。

设置网页事件队列上限 : 字符串 → 整数 → 有

设置某类型的队列上限,1 至 65536;类型名不在上表或上限越界时抛事故。调低上限时,多出的旧事件在该类型下一次有新事件到达时被丢弃并计数。

限额与失败分类

| 项 | 限额 | | --- | --- | | 队列上限(每类型) | 默认 1024,可配 1 至 65536 | | 订阅数 | 界面订阅至多 256,消息订阅至多 128(见网页消息包) | | 键规则条数 | 至多 32 | | 事件名长度 | 至多 64 个字符 | | 事件里的 值 | 至多 65536 个 UTF-16 码元,超过则省略并标记 | | 消息详情 | 至多 1 MiB |

失败分类:违规调用(目标不存在、事件名或策略非法、订阅超限、队列上限越界、类型名无效)抛豫言事故,消息为中文;队列满不是失败,按上文丢弃并计数;宿主关闭不是失败,等待返回“关闭”事件。

跨宿主一致性

  • 事件按宿主收到的顺序排队,同一订阅号的事件保持先后。
  • 序号在宿主实例内单调递增;丢弃只发生在队列头部(最旧)方向,不会打乱余下事件的顺序。
  • 键规则匹配、操作键 搜索和委托规则必须与本文一致;字段缺省与省略规则必须一致,应用应把缺失字段视为“不适用”。
  • 不能提供同步拦截的宿主不得声明本包。

一致性样例

  1. 订阅 容器 的 click;点击容器内无 ID、带 data-yy="打开:7" 按钮里的 span;等待网页事件 返回 类型 为“界面”、标识 为空、操作键 为 打开:7。
  2. 订阅 文档 的 keydown,策略 {"键规则":[{"键":"Enter","shift":false}]};派发 Enter 后,dispatchEvent 一返回,事件的 defaultPrevented 即为真;派发 Shift+Enter 则为假;两者的事件都送达,已阻止默认 分别为真、假。
  3. 把界面队列上限设为 5,连续点击 20 次而不取事件:队列里留下最新 5 个,读取网页事件丢弃数 为 15,留下事件的序号连续且大于 15。
  4. 应用正阻塞在 等待界面事件 时到达一条页面消息:消息留在队列里,随后 等待网页消息(或 等待网页事件)取到,界面事件到达后 等待界面事件 返回。
  5. 订阅 文档 的 click,策略 {"捕获":true,"选择器":"a[href]","阻止默认":true,"带属性":["href"]};点击 <a href="/x"><span>字</span></a> 里的 span:事件到达,标识 为 a 的 ID(可为空)、目标标识 为 span 的 ID、属性 为 {"href":"/x"},且 dispatchEvent 一返回,defaultPrevented 即为真;点击页面上的普通按钮:没有事件,defaultPrevented 为假。
  6. 订阅策略 {"选择器":"button["}:订阅时抛事故,宿主不留下任何监听。
  7. 订阅 文档 的 click,策略 {"排除选择器":".文件目录"}:点击 .文件目录 内的任意后代没有事件;点击页面上其他位置有事件(标识 等仍取原始目标)。

边界

本包不规定:网络请求、定时器的创建与取消、SSE 连接、本地编译(它们分属网页请求、网页定时、网页事件源、网页编译包,只借用本包的事件类型);不提供跨窗口、跨 Worker 事件;不提供“事后阻止默认”;不向应用暴露原生事件对象或 DOM 句柄;不保证高频事件(滚动、指针移动)逐个送达(默认合并);触屏、拖放、剪贴板 paste 的载荷字段留待后续版本。

依赖

查看包配置

上传历史

当前上传修订:1 ·

  1. 上传修订 1
    下载 ZIP

包层级

父包:豫言操作系统接口

没有子包。