c5a503580c
承 Tier1(S2+S3,已於 main 168e9e7)。本批為效能第二層+第三層,全程獨立複驗(差分/往返/ 增量≡全量/進程隔離測速/真瀏覽器)+4 份真人撰寫真實文檔驗收,行為保真、提速真實。 掃描層(無狀態、保行為): - 中間發現+S4:insidePill/isAvoided 之「逐文本節點沿祖先鏈」以 ancestorOrSelfPill/…Avoided 迭代回填記憶化(每元素恰算一次、兄弟共用祖先鏈快取),O(文本×depth)→O(元素)。實測掃描層 el.matches() 累計約降五成;深嵌套 render 進程隔離墻鐘 pre-Tier1 707ms→254ms(2.8×)。 佈局層(僅瀏覽器、正確性敏感): - S1 gapTrim 對齊門:尾隨 margin 僅在右緣 flush 之對齊可見,唯 left/start(ltr) 之 ragged 右緣 可整批跳過,省其 O(gaps) 次同步重排。實測非兩端對齊內容 gapTrim 數千 ms→數十 ms;justify/ center/right/RTL 保留完整逐一定案、行為不變(真瀏覽器差分:justify trim 集指紋逐位相同)。 增量恢復(第三層,向下兼容 API): - C1 持久化 Finder:跨 render 復用元素鍵快取(category/level/feature/祖先游走)。正確性靠 失效:新增 finder.invalidate(el) 清整棵子樹快取(祖先屬性變則後代快取變脏),revert 自動失效 其子樹(安全預設)。 - C3 rerender(root)=revert+render;局部恢復(實測編輯 400 段中一段 766×、matches 1441×)。 - C2 observe/disconnect:MutationObserver 自動增量——內容/分類屬性變化重處理最近塊、新增之塊 直接 render;render/revert 期間暫停觀察,自身注入絕不自觸發。 - append 修復(IV-3 差分複驗發現、單測漏掉):向葉塊(帶 jz-orphan)追加子塊使其轉非葉,須撤 容器陳舊 orphan——追加塊時若塊級父有 jz-orphan 直接子則改重處理該父。 測試 130→140(+S2 非葉塊、C1/C3、C2 觀察/失效/append、不自觸發)。基準設施:bench-incremental (增量恢復)、bench-layout(版面層,headless Chrome)。dist 重建。
141 lines
9.1 KiB
TypeScript
141 lines
9.1 KiB
TypeScript
import type { FeatureLevel, ResolvedOptions } from "../types.js";
|
||
/** 邏輯 run 中之單一字元來源位置。 */
|
||
export interface CharPos {
|
||
textNode: Text;
|
||
offset: number;
|
||
}
|
||
/** 一段邏輯文本:剝離樣式後之連續字元,及其逐字回映射。 */
|
||
export interface LogicalRun {
|
||
text: string;
|
||
map: CharPos[];
|
||
}
|
||
/** pill 鄰接之邏輯鄰字。 */
|
||
export interface AdjacentChar {
|
||
textNode: Text;
|
||
offset: number;
|
||
ch: string;
|
||
}
|
||
/** 行內元素對排版之口徑分類(§4.1)——恰屬其一,優先序見 Finder.category。 */
|
||
export type InlineCategory = "avoid" | "pill" | "isolate" | "block" | "transparent" | "opaque";
|
||
export declare class Finder {
|
||
private readonly opts;
|
||
private readonly styleSet;
|
||
private readonly blockSet;
|
||
private readonly skipSet;
|
||
private readonly pillSelector;
|
||
private readonly isolateSelector;
|
||
private readonly avoidSelector;
|
||
private readonly includeSelector;
|
||
private readonly skipAttr;
|
||
private readonly userIsSkipped;
|
||
private readonly levelTextSelector;
|
||
private readonly featureCache;
|
||
private readonly levelCache;
|
||
private readonly categoryCache;
|
||
private readonly pillAncestorCache;
|
||
private readonly avoidAncestorCache;
|
||
constructor(opts: ResolvedOptions);
|
||
/**
|
||
* 清除 el 及其**整棵子樹**之元素鍵快取(C1 失效原語)。持久化 Finder(跨 render 復用快取)
|
||
* 下,元素之屬性或內容變化會令其分類(category)/級別(level)/功能(feature)/祖先游走
|
||
* (pill/avoid ancestor)快取失效。**須清整棵子樹、非僅該元素**:level/feature/祖先游走
|
||
* 之結果依賴祖先鏈,某祖先之 data-jz-level/data-juzhen/class 變化會令其**後代**之快取值變脏,
|
||
* 即便後代自身未動。querySelectorAll('*') 走子樹(O(子樹),native、無遞迴棧風險)。移除之元素
|
||
* 由 WeakMap 自動回收,無需顯式清。 */
|
||
invalidate(el: Element): void;
|
||
private forget;
|
||
/** 該元素是否匹配 pill 選擇器(三分類之「整體」:code/kbd/行內公式等)。 */
|
||
pillMatches(el: Element): boolean;
|
||
/** 該元素是否匹配 isolate 選擇器(三分類之「隔離」:帶盒樣式之行內,如背景色
|
||
* <mark>)。邊界口徑同 pill(斷 run/斷相鄰/不可切分/兩端補隙),內部照常處理。 */
|
||
isolateMatches(el: Element): boolean;
|
||
/** 節點是否位於 pill **內部**(僅查祖先,不含自身)。pill 為整體元素:內部對
|
||
* 一切 pass 不可見——不 charify、不收 run、不切分、不於其內插間隙(下游問題六:
|
||
* .katex 內部曾被 jiya/spacing 直接變更)。 */
|
||
insidePill(node: Node): boolean;
|
||
/** el 或其祖先鏈任一為 pill(記憶化)。沿祖先鏈迭代(非遞迴,任意深度棧安全)至最近之
|
||
* 已快取/pill 命中/鏈頂,途中非 pill 元素入棧、以斷點之值一次回填——每元素恰算一次,
|
||
* 兄弟共用祖先鏈快取。insidePill 為 pillMatches 之最大來源(S3 延伸)。 */
|
||
private ancestorOrSelfPill;
|
||
/**
|
||
* 行內元素分類(§4.1)——一元素對排版之口徑,**恰屬其一**。**優先序**(依序短路,即多
|
||
* 選擇器同命中時之定案規則):`avoid > pill > isolate > block > transparent > opaque`
|
||
* (opaque=未在 styleInlines 之未知行內標籤,嚴格白名單下當隔離式邊界)。故:同時命中
|
||
* pill 與 isolate → **pill**(pill 覆蓋 isolate);帶 data-jz-skip 之 pill/isolate → **avoid**
|
||
* (自身排除最高)。**邏輯文本流之遍歷原語**(collectRuns/jiyaAdjacency/adjacentLogicalChar
|
||
* 之 isTransparent)皆據此**單一分類**分派;core/split 之 canSplit = SPLITTABLE 白名單 ∩
|
||
* transparent(結構切分為獨立層,故更窄)。
|
||
*
|
||
* **記憶化**(S3):分類含至多三次 el.matches()(pill/isolate/avoid 選擇器),且被
|
||
* collectRuns/jiyaAdjacency/jinze/orphan/split 各 pass 逐元素重算——以 categoryCache
|
||
* 記憶化砍除跨 pass 重複。安全性:單次 render 內一元素之分類**恆定**(class/skip 屬性/
|
||
* nodeName 皆不因 render 變更;jz-* 包裹為新建元素、按 nodeName 分類亦恆定),且 Finder
|
||
* 隨 makeCtx 每次 render 重建 → 快取僅存活單次 render,無跨 render 失效問題(持久化屬 C1,
|
||
* 另議)。
|
||
*/
|
||
category(el: Element): InlineCategory;
|
||
private computeCategory;
|
||
/** category 之透明判定末步:styleInlines 標籤,或聚珍自身之 wrap 類 jz-*(jz-hws 間隙、
|
||
* jz-jinze 禁則群組、**jz-char/jz-inner 標點原子**)。前四步已排除 avoid/pill/isolate/block。
|
||
* jz-char/jz-inner 必須列入:否則 adjacentLogicalChar 之 descend 遇標點原子既不進入亦不
|
||
* 終止、而**跳過**它續找更前之字(`汉字。<code>` 綁定後 pill 左鄰字應為「。」卻誤取「字」
|
||
* 補 jz-hws);jz-char 僅含標點(非 pillNeighbor),透明後 descend 回傳標點、不補隙。 */
|
||
private isTransparentTag;
|
||
/** el 是否為邏輯文本流可透明穿越者(= category "transparent")。 */
|
||
isTransparent(el: Element): boolean;
|
||
/** 該元素**自身**是否為 avoid 邊界。avoid 之七種來源,依序短路判定、任一命中即是:
|
||
* ① isSkipped 謂詞(消費端函數) ② skipTags 標籤名單(PRE/SAMP/SCRIPT…,可覆寫)
|
||
* ③ SVG 命名空間 ④ contentEditable
|
||
* ⑤ data-jz-skip 屬性(內建) ⑥ skipAttribute(消費端指定屬性名)
|
||
* ⑦ scope.avoid 選擇器
|
||
* **此為自身判定**,供遍歷原語(collectRuns/jiyaAdjacency/canSplit/isTransparent)於
|
||
* 下探時逐元素自判(祖先由遞迴涵蓋)。**一次性**問「某節點是否被 avoid(自身或祖先)」
|
||
* 請改用 isAvoided(單一組合入口),勿只用本方法而漏判祖先、或只用 isAvoided 而漏判自身
|
||
* (下游問題六/階段一 #2:漏一半致 avoid 對自身形同虛設)。 */
|
||
avoidsSelf(el: Element): boolean;
|
||
/** 某節點是否被 avoid——**自身即邊界**(元素經 avoidsSelf 之七源;非元素節點僅判
|
||
* isSkipped)**或**祖先鏈上任一為 avoid 邊界。此為「是否 avoid」之**單一組合入口(安全
|
||
* 預設)**:一次性檢查用此,免除「avoidsSelf ‖ isAvoided」易漏一半之誤用。遍歷原語逐
|
||
* 元素下探時則用 avoidsSelf(僅自身,祖先由遞迴涵蓋)。 */
|
||
isAvoided(node: Node): boolean;
|
||
/** el 或其祖先鏈任一為 avoid 邊界(記憶化,迭代回填,同 ancestorOrSelfPill)。 */
|
||
private ancestorOrSelfAvoided;
|
||
/** scope.include 啟用判定:未設則恆 true;設則須有命中祖先(opt-in)。 */
|
||
inScope(node: Node): boolean;
|
||
/** 走訪 root 下所有「可處理」文本節點(已過濾 avoid / pill 內部 / 非作用域)。 */
|
||
eachTextNode(root: Element, cb: (node: Text) => void): void;
|
||
/** 走訪 root 下所有 block 元素(含 root 本身若為 block)。 */
|
||
eachBlock(root: Element, cb: (block: Element) => void): void;
|
||
/**
|
||
* 收集 block 之邏輯文本 run(架構 §4.1,I7):透明行內(isTransparent:styleInlines
|
||
* + wrap 類 jz-*)穿越、run 延續;block/pill/avoid 中斷 run;isolate 與**未知行內
|
||
* 標籤**(嚴格白名單)中斷 run 且內部自成獨立 run。回傳 text.length >= 2 之 run。
|
||
*/
|
||
collectRuns(block: Element): LogicalRun[];
|
||
/**
|
||
* pill 之邏輯鄰字(架構 §4.1):經樣式鏈遞迴,遇 pill/block/avoid/間隙
|
||
* marker 即止。direction = -1 取前鄰字,+1 取後鄰字。
|
||
*/
|
||
adjacentLogicalChar(el: Element, direction: -1 | 1): AdjacentChar | null;
|
||
/**
|
||
* 節點之功能級別(§6.5.0a)。自 node 向上找**最近**之顯式信號:元素屬性
|
||
* `data-jz-level="text|paragraph"`(最優先),或命中 `level.text` 選擇器(→ text);
|
||
* 皆無則預設 paragraph(全功能)。結果以解析後之元素為鍵快取。
|
||
*/
|
||
levelFor(node: Node): FeatureLevel;
|
||
/**
|
||
* 某級別之 pass 是否可作用於該節點(§6.5.0a gate)。文本級 pass 不受限(恆 true);
|
||
* 段落級 pass 僅作用於段落級元素(text-level 子樹跳過)。與 featureEnabledFor 疊加:
|
||
* level 為粗粒度語義組(先判),blocks/屬性為細粒度逐功能(後調)。
|
||
*/
|
||
levelAllows(node: Node, passLevel: FeatureLevel): boolean;
|
||
/**
|
||
* 解析節點對某功能之有效啟用狀態(§3.6,per-node 閘)。向上找最近帶
|
||
* 功能指令之祖先(data-juzhen 白名單 / data-juzhen-off 黑名單 / 命中
|
||
* blocks 選擇器),無則回退全域 features。結果以元素為鍵快取。
|
||
*/
|
||
featureEnabledFor(node: Node, name: string): boolean;
|
||
private globalFeature;
|
||
private resolveFeature;
|
||
}
|