perf: publish certified scroll viewports without document planning
Some checks are pending
CI / test (29.1) (push) Waiting to run
CI / test (30.2) (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
Some checks are pending
CI / test (29.1) (push) Waiting to run
CI / test (30.2) (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
This commit is contained in:
parent
efdc35f4af
commit
993e09be6b
@ -54,6 +54,16 @@ roots may reuse their complete composed content and render only the wrapper.
|
||||
Pure root offset changes preserve already composed portal and anchor content.
|
||||
Style, placement, content and display-context changes must invalidate that proof.
|
||||
|
||||
Proved scroll-only changes build a complete candidate from the published state
|
||||
and a local immutable index delta, bypassing general declarative preparation.
|
||||
Surface stages rich replacement text and exact old/new character ranges, then
|
||||
returns an authenticated TP commit batch with complete target mount specs;
|
||||
it does not build a surface plan. Equal text lengths never authorize reuse of
|
||||
old mounts: newly visible rows may have different owners and interaction tags.
|
||||
Owner resolution can reuse a private cache while its topology and TP object
|
||||
indexes remain valid. Attribute updates and layout reflow retain their own
|
||||
preparation paths; only terminal publication and rollback are shared.
|
||||
|
||||
A nested owner can replace its viewport within a proven unchanged allocation,
|
||||
without rendering unrelated siblings. With one unchanged root scroll ancestor,
|
||||
the update must also patch that ancestor's retained content, ownership membership
|
||||
@ -68,6 +78,14 @@ freshly rebound producers. A producer must not mutate a published continuation
|
||||
before TP commits. This isolation can rebuild the target prefix on a miss;
|
||||
cached-hit timings do not describe that cost.
|
||||
|
||||
The prepared viewport batch is consumed once inside the existing scoped TP
|
||||
transaction. Subsequent signal-driven evaluation uses the current committed
|
||||
source and ordinary projection. A proof miss chooses the ordinary path before
|
||||
publication; a publication error propagates through rollback without retrying
|
||||
through another renderer. General preparation and surface-plan counters in the
|
||||
shared evaluator verify the bypass separately from TP bookkeeping and buffer
|
||||
write time; this path does not establish constant cost in total object count.
|
||||
|
||||
Scroll publication still goes through TP and the Ebox participant. Rejection
|
||||
preserves the old text, properties, offsets, raw and rendered caches, source
|
||||
nodes, indexes, objects, mounts and report. The focused regression targets are
|
||||
|
||||
@ -53,6 +53,13 @@ layered 或带 chrome 的 root 可以复用完整合成内容,只渲染 wrappe
|
||||
变化保留已经合成的 portal 和 anchor 内容;style、placement、content 或 display
|
||||
context 变化必须使该证明失效。
|
||||
|
||||
通过证明的纯滚动变更从已发布状态与局部不可变 index delta 构造完整 candidate,
|
||||
跳过通用声明式准备。Surface 提前生成带属性的替换文本及精确的新旧字符范围,
|
||||
再通过带完整目标 mount specs 的认证 TP commit batch 发布,不构造 surface plan。
|
||||
文本等长不能证明旧 mount 可复用:新进入视口的行可能具有不同 owner 与交互 tag。
|
||||
Topology 与 TP object index 有效时,可复用私有 owner 解析缓存。属性更新与布局
|
||||
重排仍使用各自的准备路径,只共享最终发布与回滚边界。
|
||||
|
||||
Nested owner 在 allocation 不变且通过证明时,可以只替换自己的 viewport,避免渲染
|
||||
无关兄弟节点。若存在一个 offset 不变的 root scroll ancestor,还必须更新该祖先的
|
||||
保留内容、ownership membership 和 line index,并将 producer 重新绑定到 candidate
|
||||
@ -64,6 +71,12 @@ Nested owner 在 allocation 不变且通过证明时,可以只替换自己的
|
||||
不得在 TP commit 前修改已发布的 continuation。该隔离可能在 miss 时重建目标 prefix,
|
||||
因此 cached-hit 耗时不能代表 miss 的代价。
|
||||
|
||||
预备的 viewport batch 在现有 scoped TP transaction 中只消费一次。后续 signal
|
||||
触发的求值读取当前已提交 source,使用普通 projection。证明失败在发布前选择普通
|
||||
路径;发布错误直接进入 rollback,不换另一个 renderer 重试。共享 evaluator 的
|
||||
通用准备与 surface-plan 计数分别验证这两项工作已跳过;TP bookkeeping 与实际
|
||||
buffer 写入另行测量。该路径并不保证耗时与总 object 数无关。
|
||||
|
||||
Scroll publication 仍经过 TP 与 Ebox participant。发布失败时,旧文本、属性、offset、
|
||||
raw/rendered cache、source node、index、object、mount 和 report 均保持不变。聚焦回归
|
||||
入口为 `make layer-tests`、`make nested-scroll-tests`、`make scroll-cold-tests`
|
||||
|
||||
@ -233,6 +233,17 @@
|
||||
:rollback discard-unreferenced-candidates
|
||||
:rebuild-proof reproject-owner-native-hover-face
|
||||
:cleanup garbage-collect-unreferenced-faces)
|
||||
(:id scroll-viewport-owner-cache
|
||||
:storage (:scroll-viewport-owner-cache)
|
||||
:current-contract candidate-private-resolved-owner-table
|
||||
:target-contract discardable-derived-values
|
||||
:category disposable-cache
|
||||
:owner ebox-surface
|
||||
:mutation-api copy-before-candidate-fill
|
||||
:generation-binding unchanged-parent-region-and-tp-object-indexes
|
||||
:rollback discard-candidate-cache
|
||||
:rebuild-proof resolve-rich-line-roles-against-retained-topology
|
||||
:cleanup ordinary-projection-or-generation-replacement)
|
||||
(:id derived-caches
|
||||
:storage (ebox--box-content-render-cache ebox--char-width-cache
|
||||
ebox--display-signature-cache ebox--face-height-width-cache
|
||||
|
||||
177
ebox-surface.el
177
ebox-surface.el
@ -184,6 +184,7 @@ the winning client state; otherwise only the candidate is disposable."
|
||||
(owner key compute value dependencies &rest options))
|
||||
(declare-function ebox--record-render-output-provenance
|
||||
"ebox-render-context" (rendered))
|
||||
(declare-function ebox--strip-paint-origins! "ebox-render-context" (string))
|
||||
(declare-function ebox--render-owned-text-value-p
|
||||
"ebox-render-context" (property value &optional registry))
|
||||
(declare-function ebox-incremental--hash-snapshot
|
||||
@ -2424,10 +2425,13 @@ FRAMEWORK-PARTICIPANT retains post-TP publication facts when non-nil."
|
||||
(signals (car signals-result))
|
||||
(signals-created-p (cdr signals-result))
|
||||
(producer
|
||||
(if (plist-get state-overrides :scroll-viewport-batch)
|
||||
(ebox-surface--scroll-viewport-producer
|
||||
old-state state-overrides signals)
|
||||
(ebox-surface-producer
|
||||
source old-state t state-overrides signals projection-kind
|
||||
source-isolated-p
|
||||
(plist-get state-overrides :source-path-copied-p)))
|
||||
(plist-get state-overrides :source-path-copied-p))))
|
||||
success native-settled-p)
|
||||
(when (and (null on-mismatch)
|
||||
(or (plist-get state-overrides :owner-scoped-proofs)
|
||||
@ -5275,6 +5279,173 @@ copies where needed. Detached owners keep their fractional-origin guard."
|
||||
0 (length output) (list 'ebox-scroll-window region-id) output))
|
||||
output))))
|
||||
|
||||
(defun ebox-surface--prepare-scroll-viewport (buffer previous-state state)
|
||||
"Stage STATE's exact rich viewport without document or surface planning.
|
||||
PREVIOUS-STATE remains the published generation in BUFFER. Existing root
|
||||
wrapper and nested allocation proofs select the output; a proof miss returns
|
||||
nil before any TP publication. The resulting ordinary TP batch contains only
|
||||
the proved replacement coordinates, including an enclosing cache's separate
|
||||
content-coordinate update when present."
|
||||
(with-current-buffer buffer
|
||||
(save-restriction
|
||||
(widen)
|
||||
(let* ((nested (plist-get state :nested-scroll-patch-proof))
|
||||
(output
|
||||
(if nested
|
||||
(when (and (not (plist-get previous-state :fractional-pixel-output-p))
|
||||
(or (not (plist-get nested :scroll-content-region-id))
|
||||
(plist-get state :scroll-content-proof)))
|
||||
(unless (plist-get state :owner-scoped-proofs)
|
||||
(plist-put state :owner-scoped-proofs (list nested)))
|
||||
(let ((ebox-surface--integral-local-output-required-p t))
|
||||
(catch 'ebox/fractional-local-output
|
||||
(ebox-surface--span-patch-output buffer previous-state state))))
|
||||
(ebox-surface--scroll-patch-output buffer state t))))
|
||||
(when (and (stringp output)
|
||||
(not (plist-get state :paint-property-contributions)))
|
||||
(unless (plist-get state :scroll-patch-lines)
|
||||
(plist-put state :scroll-viewport-fragments
|
||||
(or (plist-get state :span-patch-fragment-data)
|
||||
(ebox-surface--rendered-fragments output))))
|
||||
(ebox--strip-paint-origins! output)
|
||||
(let* ((base-extent (- (point-max) (point-min)))
|
||||
(target-extent (length output))
|
||||
(coordinates
|
||||
(or (plist-get state :content-coordinate-patches)
|
||||
(list (list :old-start 0 :old-end base-extent
|
||||
:new-start 0 :new-end target-extent))))
|
||||
(patches
|
||||
(mapcar
|
||||
(lambda (patch)
|
||||
(append patch
|
||||
(list :replacement
|
||||
(substring output (plist-get patch :new-start)
|
||||
(plist-get patch :new-end)))))
|
||||
coordinates))
|
||||
(batch
|
||||
(condition-case nil
|
||||
(tp-commit-batch-create
|
||||
:base-revision (plist-get state :runtime-revision)
|
||||
:target-revision (1+ (plist-get state :runtime-revision))
|
||||
:base-extent base-extent :target-extent target-extent
|
||||
:patches patches :coordinate-patches coordinates)
|
||||
(tp-surface-error nil))))
|
||||
(when batch
|
||||
(plist-put state :scroll-viewport-batch batch)
|
||||
output)))))))
|
||||
|
||||
(defun ebox-surface--scroll-viewport-owner-cache (previous-state state)
|
||||
"Return a private owner-resolution cache for STATE's unchanged topology.
|
||||
Reuse requires PREVIOUS-STATE's exact parent, region and TP object indexes.
|
||||
The scroll-only source proof preserves their meanings when rebinding copied
|
||||
Boxes; ordinary document projection discards this optional derived cache."
|
||||
(let* ((old (plist-get previous-state :scroll-viewport-owner-cache))
|
||||
(keys '(:parent-table :region-node-table
|
||||
:surface-node-object-table :region-surface-object-table))
|
||||
(valid (and old
|
||||
(cl-every (lambda (key)
|
||||
(eq (plist-get old key) (plist-get previous-state key)))
|
||||
keys)))
|
||||
(cache (list :owners
|
||||
(if valid (copy-hash-table (plist-get old :owners))
|
||||
(make-hash-table :test #'equal)))))
|
||||
(dolist (key keys)
|
||||
(plist-put cache key (plist-get state key)))
|
||||
cache))
|
||||
|
||||
(defun ebox-surface--scroll-viewport-result (context previous-state state output batch)
|
||||
"Publish STATE's proved viewport OUTPUT and BATCH in the active CONTEXT.
|
||||
Reuse PREVIOUS-STATE's keyed topology and resolve rich roles against that
|
||||
unchanged topology. Every batch supplies complete target ownership, since
|
||||
equal character lengths do not imply equal visible owners or interaction."
|
||||
(let* ((surface-root (tp-object-ensure context nil ebox-surface--root-key 'ebox/surface))
|
||||
(node-root (tp-object-ensure context surface-root ebox-surface--nodes-key 'ebox/nodes))
|
||||
(fragment-root (tp-object-ensure context surface-root
|
||||
ebox-surface--fragments-key 'ebox/fragments))
|
||||
(text-leaf (tp-object-ensure context fragment-root ebox-surface--text-key 'ebox/text))
|
||||
(node-objects (plist-get state :surface-node-object-table))
|
||||
(region-objects (plist-get state :region-surface-object-table))
|
||||
(lines (plist-get state :scroll-patch-lines))
|
||||
(fragments
|
||||
(unless lines
|
||||
(plist-get state :scroll-viewport-fragments)))
|
||||
(_cache
|
||||
(plist-put state :scroll-viewport-owner-cache
|
||||
(ebox-surface--scroll-viewport-owner-cache previous-state state)))
|
||||
(owned-ranges
|
||||
(if lines
|
||||
(ebox-surface--scroll-owned-ranges
|
||||
context text-leaf lines state node-objects region-objects nil)
|
||||
(ebox-surface--owned-ranges
|
||||
context text-leaf fragments state node-objects region-objects nil)))
|
||||
(length (length output))
|
||||
(records
|
||||
(list (list :object text-leaf :start 0 :end length :props nil :tags nil :leaf t)
|
||||
(list :object fragment-root :start 0 :end length :props nil :tags nil :leaf nil)
|
||||
(list :object surface-root :start 0 :end length :props nil :tags nil :leaf nil))))
|
||||
(tp-object-reuse-subtree context node-root)
|
||||
(plist-put state :surface-owned-ranges owned-ranges)
|
||||
(plist-put state :surface-fragments
|
||||
(if lines (list :scroll-lines (copy-sequence lines))
|
||||
(ebox-surface--fragment-metadata fragments state)))
|
||||
(dolist (key '(:scroll-viewport-output :scroll-viewport-batch :scroll-viewport-fragments
|
||||
:content-coordinate-patches :content-base-extent
|
||||
:span-patch-fragment-data :span-patch-content-p
|
||||
:span-patch-retained-owned-ranges :scroll-patch-lines
|
||||
:nested-scroll-patch-proof :scroll-content-proof
|
||||
:owner-scoped-proofs :scroll-state-transaction
|
||||
:scroll-fast-window-p :scroll-patch-region-id :span-patch-owner-id
|
||||
:preserve-scroll-producer-region-ids :previous-surface-owned-ranges))
|
||||
(cl-remf state key))
|
||||
(plist-put state :retained-scroll-content-p nil)
|
||||
(tp-commit-batch-result-create
|
||||
context batch :mount-specs (ebox-surface--commit-mount-specs records owned-ranges)
|
||||
:client-state state)))
|
||||
|
||||
(defun ebox-surface--scroll-viewport-producer (previous-state state signals)
|
||||
"Return a scroll-only producer using the existing scoped transaction.
|
||||
STATE owns one prepared viewport batch after PREVIOUS-STATE. Subscribe to
|
||||
SIGNALS just like ordinary projection, but consume that batch only once.
|
||||
A later reactive evaluation renders the then-current source normally rather
|
||||
than replaying old coordinates, properties or revision authority."
|
||||
(let ((output (plist-get state :scroll-viewport-output))
|
||||
(batch (plist-get state :scroll-viewport-batch))
|
||||
(offsets (ebox-surface--scroll-offsets (plist-get state :scroll-state-table))))
|
||||
(lambda (context)
|
||||
(let* ((buffer (ebox-surface--signals-buffer signals))
|
||||
(surface (ebox-surface--live-buffer-surface buffer))
|
||||
(current (tp-surface-client-state surface))
|
||||
(prepared state)
|
||||
(base previous-state)
|
||||
(rendered output)
|
||||
(prepared-batch batch)
|
||||
(expected-offsets offsets)
|
||||
(_consume
|
||||
;; A producer remains subscribed after this evaluation. Keep
|
||||
;; only current signals in that closure, on success or fallback;
|
||||
;; later evaluations must not retain an obsolete document.
|
||||
(setq state nil previous-state nil output nil batch nil offsets nil))
|
||||
(axes (plist-get (or prepared current) :viewport-dependent-node-id-axes))
|
||||
(display (tp-signal-read (ebox-surface--signals-display signals)))
|
||||
(width (funcall (if (car axes) #'tp-signal-read #'tp-signal-peek)
|
||||
(ebox-surface--signals-viewport-width signals)))
|
||||
(height (funcall (if (cdr axes) #'tp-signal-read #'tp-signal-peek)
|
||||
(ebox-surface--signals-viewport-height signals)))
|
||||
(scroll (tp-signal-read (ebox-surface--signals-scroll signals))))
|
||||
(with-current-buffer buffer
|
||||
(if (and prepared (eq current base)
|
||||
(equal display (plist-get prepared :display-signature))
|
||||
(equal width (plist-get prepared :viewport-width))
|
||||
(equal height (plist-get prepared :viewport-height))
|
||||
(equal scroll expected-offsets))
|
||||
(ebox-surface--scroll-viewport-result
|
||||
context base prepared rendered prepared-batch)
|
||||
(ebox-surface--project
|
||||
context (ebox-tree-copy-node-structure (plist-get current :root-node))
|
||||
current t (list :runtime-revision (tp-surface-revision surface)
|
||||
:source-base-index (plist-get current :source-index))
|
||||
signals nil t nil)))))))
|
||||
|
||||
(defun ebox-surface--runtime-index-ready-p (state)
|
||||
"Return non-nil when STATE carries a complete prepared runtime index."
|
||||
(let* ((root (plist-get state :root-node))
|
||||
@ -5766,7 +5937,9 @@ VISIT-RUNS receives one callback accepting ROLE-IDS, START, END, and optional
|
||||
layout dependency region ids.
|
||||
Adjacent ranges merge only when both their owner and opaque tags match."
|
||||
(let ((active (make-hash-table :test #'eq))
|
||||
(owner-cache (make-hash-table :test #'equal))
|
||||
(owner-cache
|
||||
(or (plist-get (plist-get state :scroll-viewport-owner-cache) :owners)
|
||||
(make-hash-table :test #'equal)))
|
||||
ranges)
|
||||
(funcall
|
||||
visit-runs
|
||||
|
||||
138
ebox.el
138
ebox.el
@ -3393,10 +3393,7 @@ A miss owns its subtree, indexes, cache lines and producers before staging."
|
||||
(or (and (ebox--scroll-state-retained-window-ready-p
|
||||
staged-scroll-state) t)
|
||||
(and (plist-get runtime :nested-scroll-patch-proof) t)))
|
||||
(commit-input
|
||||
(let ((ebox--scroll-global-state table))
|
||||
(ebox-incremental-prepare-scoped-commit
|
||||
buffer root
|
||||
(report-overrides
|
||||
(append
|
||||
(ebox--constraint-change-report-props
|
||||
(ebox--region-constraint-change
|
||||
@ -3411,11 +3408,20 @@ A miss owns its subtree, indexes, cache lines and producers before staging."
|
||||
:scroll-state-transaction t
|
||||
:scroll-patch-fast-p scroll-patch-fast-p
|
||||
:native-scroll-materialize-p
|
||||
(plist-get transition :native-materialize-p)))
|
||||
t)))
|
||||
(plist-get transition :native-materialize-p))))
|
||||
(viewport-commit
|
||||
(unless (plist-get transition :native-materialize-p)
|
||||
(ebox--surface-scroll-viewport-commit
|
||||
buffer region-id runtime report-overrides)))
|
||||
(commit-input
|
||||
(or viewport-commit
|
||||
(let ((ebox--scroll-global-state table))
|
||||
(ebox-incremental-prepare-scoped-commit
|
||||
buffer root report-overrides t))))
|
||||
(_staged-cache
|
||||
(unless viewport-commit
|
||||
(ebox--surface-restore-staged-scroll-cache
|
||||
commit-input region-id staged-scroll-state))
|
||||
commit-input region-id staged-scroll-state)))
|
||||
(scope-node-ids
|
||||
(or (plist-get commit-input :scope-node-ids)
|
||||
(list owner-id)))
|
||||
@ -3455,6 +3461,124 @@ A miss owns its subtree, indexes, cache lines and producers before staging."
|
||||
(ignore surface)
|
||||
(plist-get transition :result))))
|
||||
|
||||
(defun ebox--surface-scroll-viewport-commit
|
||||
(buffer region-id runtime report)
|
||||
"Prepare BUFFER's REGION-ID retained viewport from isolated RUNTIME.
|
||||
Return an ordinary scoped commit input, or nil before publication when the
|
||||
document, display context or allocation needs general preparation. REPORT
|
||||
describes the scroll intent; Surface supplies exact text and ownership.
|
||||
The local index delta preserves immutable source facts and rebinds copied
|
||||
Boxes without publishing RUNTIME's deliberately sparse staging indexes."
|
||||
(let* ((base (plist-get runtime :base-state))
|
||||
(root (plist-get runtime :root))
|
||||
(old-root (plist-get base :root-node))
|
||||
(owner (plist-get report :owner-id))
|
||||
(root-p (equal owner (plist-get root :node-id)))
|
||||
(nested-proof (plist-get runtime :nested-scroll-patch-proof))
|
||||
(old-scroll (gethash region-id (plist-get base :scroll-state-table))))
|
||||
(when (and owner old-scroll
|
||||
(ebox-surface--runtime-index-ready-p base)
|
||||
(hash-table-p (plist-get base :surface-node-object-table))
|
||||
(hash-table-p (plist-get base :region-surface-object-table))
|
||||
(or root-p nested-proof)
|
||||
(not (plist-get base :native-sync-session))
|
||||
(not (plist-get base :native-sync-confirmed-p))
|
||||
(not (plist-get old-scroll :lazy-scroll-prefix-dirty))
|
||||
(not (plist-get old-scroll :lazy-scroll-window-refresh-required))
|
||||
(not (plist-get old-scroll :native-reflow-target-prefix-p))
|
||||
(not (plist-get old-scroll :native-reflow-materialize-p))
|
||||
(zerop (ebox-tree-author-style-count old-root))
|
||||
(zerop (ebox-tree-author-style-count root))
|
||||
(equal (plist-get base :display-signature)
|
||||
(ebox--current-display-signature))
|
||||
(eq (not (null (plist-get base :cascade-active-p)))
|
||||
(not (null (ebox-style-cascade-active-p))))
|
||||
(or (not (plist-get base :cascade-active-p))
|
||||
(equal (plist-get base :stylesheet-signature)
|
||||
(ebox-surface--stylesheet-signature))))
|
||||
(let* ((delta (ebox-incremental--candidate-local-index-delta base root))
|
||||
(dirty (plist-get delta :dirty-set))
|
||||
(index (plist-get delta :index)))
|
||||
(when (and (null (plist-get delta :removed-node-ids))
|
||||
(cl-every
|
||||
(lambda (entry)
|
||||
(and (equal owner (plist-get entry :node-id))
|
||||
(not (plist-get entry :children))
|
||||
(cl-every
|
||||
(lambda (key)
|
||||
(memq key '(:scroll-offset :ebox-scroll-offset-controlled-p)))
|
||||
(plist-get entry :changed-keys))))
|
||||
dirty))
|
||||
(let* ((candidate (ebox--render-state-install-index (copy-sequence base) index))
|
||||
(table (plist-get runtime :scroll-table))
|
||||
(prepared (list :root root :dirty-set dirty :scroll-state-table table))
|
||||
(structural-caches
|
||||
(ebox-incremental--candidate-structural-caches base index dirty delta)))
|
||||
(dolist (key '(:owner-scoped-proofs :scroll-content-proof
|
||||
:content-coordinate-patches :content-base-extent
|
||||
:span-patch-fragment-data :span-patch-content-p
|
||||
:span-patch-retained-owned-ranges :scroll-patch-lines
|
||||
:paint-property-contributions :previous-surface-owned-ranges
|
||||
:mixed-owner-proof :mixed-owner-fragment-data
|
||||
:mixed-owner-content-p :formatting-context-reflow-proof
|
||||
:layer-recompose-p :native-scroll-materialize-p
|
||||
:native-buffer-scroll-p :native-render-fallback))
|
||||
(cl-remf candidate key))
|
||||
(ebox-incremental--isolate-candidate-caches candidate)
|
||||
(plist-put candidate :root-node root)
|
||||
(plist-put candidate :scroll-state-table table)
|
||||
(plist-put candidate :runtime-index-prepared-p t)
|
||||
(plist-put candidate :runtime-revision
|
||||
(1+ (or (plist-get base :runtime-revision) 0)))
|
||||
(plist-put candidate :selector-index-stale-p
|
||||
(plist-get index :selector-index-stale-p))
|
||||
(plist-put candidate :layout-snapshots (make-hash-table :test 'equal))
|
||||
(plist-put candidate :layout-snapshots-complete-p nil)
|
||||
(plist-put candidate :layout-snapshot-detail-generation
|
||||
(1+ (or (plist-get base :layout-snapshot-detail-generation) 0)))
|
||||
(plist-put candidate :retained-allocation-certificates nil)
|
||||
(plist-put candidate :reflow-prewarm-scratch nil)
|
||||
;; Quantization registers nested mutable property-value tables.
|
||||
;; A viewport candidate owns that registry before touching even
|
||||
;; one fractional carrier, including when TP later rejects it.
|
||||
(plist-put candidate :render-owned-text-values (make-hash-table :test #'eq))
|
||||
(plist-put candidate :flex-content-min-widths (make-hash-table :test #'eq))
|
||||
(dolist (key '(:render-signature-cache :viewport-height-dependent-subtree-cache))
|
||||
(plist-put candidate key (plist-get structural-caches key)))
|
||||
(plist-put candidate :projection-kind 'scroll-patch)
|
||||
(plist-put candidate :scroll-state-transaction t)
|
||||
(plist-put candidate :scroll-patch-region-id region-id)
|
||||
(plist-put candidate :span-patch-owner-id owner)
|
||||
(plist-put candidate :scroll-fast-window-p t)
|
||||
(plist-put candidate :retained-scroll-content-p root-p)
|
||||
(plist-put candidate :preserve-scroll-producer-region-ids (list region-id))
|
||||
(plist-put candidate :nested-scroll-patch-proof nested-proof)
|
||||
(let ((ebox--render-source-index (plist-get candidate :source-index))
|
||||
(ebox--render-source-generations
|
||||
(list (plist-get candidate :source-index) (plist-get base :source-index)))
|
||||
(ebox--render-source-states (list candidate base)))
|
||||
(when (plist-get nested-proof :scroll-content-region-id)
|
||||
(when-let* ((proof (ebox-incremental--scroll-content-span-proof
|
||||
buffer base prepared candidate owner)))
|
||||
(plist-put candidate :owner-scoped-proofs (list proof))
|
||||
(plist-put candidate :scroll-content-proof
|
||||
(plist-get proof :scroll-content-proof))))
|
||||
(when-let* (((or (not root-p)
|
||||
(not (or (plist-get base :layered-p)
|
||||
(plist-get base :root-portals-p)))
|
||||
(ebox-incremental--layer-root-scroll-p
|
||||
base candidate prepared owner)))
|
||||
(output (ebox-surface--prepare-scroll-viewport buffer base candidate)))
|
||||
(plist-put candidate :scroll-viewport-output output)
|
||||
(list :root root :scope-node-ids (list owner)
|
||||
:report-base
|
||||
(ebox-incremental--plist-overlay
|
||||
(ebox-incremental--layout-owner-report
|
||||
prepared (list (ebox--patch-op 'span-patch owner :dirty dirty)) candidate)
|
||||
(append report '(:projection-kind scroll-patch :full-rerender nil)))
|
||||
:projection-kind 'scroll-patch
|
||||
:state-overrides candidate)))))))))
|
||||
|
||||
(defun ebox--surface-restore-staged-scroll-cache
|
||||
(commit-input region-id staged-state)
|
||||
"Restore REGION-ID STAGED-STATE cache fields in COMMIT-INPUT."
|
||||
|
||||
@ -9,6 +9,27 @@
|
||||
(require 'ert)
|
||||
(require 'cl-lib)
|
||||
(require 'ebox)
|
||||
(require 'ebox-layer-publication-tests)
|
||||
|
||||
(defmacro ebox-layer-scroll-test--without-general-preparation (&rest body)
|
||||
"Run cached publication BODY without document preparation or surface scans."
|
||||
(declare (indent 0) (debug t))
|
||||
`(let ((stages '(ebox-incremental-prepare-scoped-commit
|
||||
ebox-incremental--prepare-declarative-runtime
|
||||
ebox--runtime-index ebox-surface--surface-plan))
|
||||
counters advices)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(dolist (stage stages)
|
||||
(let* ((counter (cons stage 0))
|
||||
(advice (lambda (&rest _) (cl-incf (cdr counter)))))
|
||||
(push counter counters)
|
||||
(push (cons stage advice) advices)
|
||||
(advice-add stage :before advice)))
|
||||
,@body)
|
||||
(dolist (entry advices) (advice-remove (car entry) (cdr entry))))
|
||||
(should (equal (nreverse counters)
|
||||
(mapcar (lambda (stage) (cons stage 0)) stages)))))
|
||||
|
||||
(defmacro ebox-layer-scroll-test--with-buffer (&rest body)
|
||||
"Run BODY in an isolated Elisp scroll buffer without native window scrolling."
|
||||
@ -55,6 +76,24 @@
|
||||
(root (plist-get runtime :root-node)))
|
||||
(gethash (plist-get root :region-id) (plist-get runtime :scroll-state-table))))
|
||||
|
||||
(defun ebox-layer-scroll-test--ordinary-input ()
|
||||
"Build an ordinary root with fractional carriers in retained physical rows."
|
||||
(ebox-build
|
||||
`(column :id "root" :width (vw 100) :height (vh 100) :overflow scroll
|
||||
,@(cl-loop for index below 80
|
||||
collect `(box :padding-left (px 1.5)
|
||||
,(format "ROW%02d" index))))))
|
||||
|
||||
(defun ebox-layer-scroll-test--materialize-root ()
|
||||
"Fully cache the root and bootstrap its retained physical rows."
|
||||
(let* ((runtime (ebox--buffer-render-state (current-buffer)))
|
||||
(state (ebox-layer-scroll-test--root-state))
|
||||
(region (plist-get (plist-get state :box) :region-id)))
|
||||
(setq state (ebox--scroll-state-materialize-lines region state))
|
||||
(puthash region state (plist-get runtime :scroll-state-table))
|
||||
(should (= 1 (ebox--scroll-region-by region 1 1)))
|
||||
(should (= -1 (ebox--scroll-region-by region -1 1)))))
|
||||
|
||||
(defun ebox-layer-scroll-test--portal-chain-input ()
|
||||
"Build two visible anchored root projections through a clipped logical owner."
|
||||
(ebox-build
|
||||
@ -93,6 +132,7 @@
|
||||
(unless (eq candidate root)
|
||||
(ebox-put candidate :scroll-offset offset)
|
||||
(ebox-put candidate :ebox-scroll-offset-controlled-p t))))
|
||||
(mounts (ebox-layer-publication-test--mount-signature (current-buffer)))
|
||||
actual fresh)
|
||||
(setq actual (buffer-string))
|
||||
;; Bind only the first layout invocation, leaving nested scroll offsets
|
||||
@ -102,7 +142,16 @@
|
||||
(lambda (candidate)
|
||||
(unless first (setq first t) (funcall set-offset candidate))
|
||||
(funcall render candidate))))
|
||||
(setq fresh (ebox-render input))))
|
||||
(with-temp-buffer
|
||||
(unwind-protect
|
||||
(progn
|
||||
(ebox-render-to-buffer (current-buffer) input)
|
||||
(setq fresh (buffer-string))
|
||||
(should (equal mounts
|
||||
(ebox-layer-publication-test--mount-signature
|
||||
(current-buffer)))))
|
||||
(when (ebox-surface-buffer-mounted-p (current-buffer))
|
||||
(ebox-unmount-buffer (current-buffer)))))))
|
||||
(should (equal (substring-no-properties actual) (substring-no-properties fresh)))
|
||||
(should (= (length actual) (length fresh)))
|
||||
(dotimes (position (length actual))
|
||||
@ -120,12 +169,18 @@
|
||||
(let* ((state (ebox-layer-scroll-test--root-state))
|
||||
(region-id (plist-get (plist-get state :box) :region-id))
|
||||
(content (plist-get state :content-lines))
|
||||
(objects (copy-hash-table
|
||||
(plist-get (ebox--buffer-render-state (current-buffer))
|
||||
:surface-node-object-table)))
|
||||
(root-renders 0)
|
||||
(render (symbol-function 'ebox-surface--render-candidate)))
|
||||
(when (eq chrome 'fractional)
|
||||
(should (plist-get (ebox--buffer-render-state (current-buffer))
|
||||
:fractional-pixel-output-p)))
|
||||
(dolist (delta '(1 1 -1 -1))
|
||||
(ebox-layer-scroll-test--without-general-preparation
|
||||
(let ((revision (ebox-surface-buffer-revision (current-buffer)))
|
||||
(before (buffer-string)))
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf root-renders)
|
||||
@ -136,6 +191,16 @@
|
||||
(should (eq (plist-get (ebox-buffer-update-report (current-buffer))
|
||||
:projection-kind)
|
||||
'scroll-patch)))
|
||||
(should (= (1+ revision)
|
||||
(ebox-surface-buffer-revision (current-buffer))))
|
||||
(should-not (equal-including-properties before (buffer-string))))
|
||||
(maphash
|
||||
(lambda (id object)
|
||||
(should (eq object
|
||||
(gethash id (plist-get
|
||||
(ebox--buffer-render-state (current-buffer))
|
||||
:surface-node-object-table)))))
|
||||
objects))
|
||||
(ebox-layer-scroll-test--assert-fresh))
|
||||
(should (= root-renders 0))))))
|
||||
|
||||
@ -185,22 +250,53 @@
|
||||
|
||||
(ert-deftest ebox-layer-scroll-rejected-publication-keeps-cache-and-input ()
|
||||
"Both publication failure stages preserve composed cache and current offset."
|
||||
(dolist (failure-step '(text client-state))
|
||||
(pcase-dolist (`(,fixture ,failure-step)
|
||||
'((layer text) (layer client-state)
|
||||
(ordinary text) (ordinary client-state)))
|
||||
(ebox-layer-scroll-test--with-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer) (ebox-layer-scroll-test--input 'fractional))
|
||||
(current-buffer) (if (eq fixture 'layer)
|
||||
(ebox-layer-scroll-test--input 'fractional)
|
||||
(ebox-layer-scroll-test--ordinary-input)))
|
||||
(when (eq fixture 'ordinary) (ebox-layer-scroll-test--materialize-root))
|
||||
(let* ((runtime (ebox--buffer-render-state (current-buffer)))
|
||||
(state (ebox-layer-scroll-test--root-state))
|
||||
(region-id (plist-get (plist-get state :box) :region-id))
|
||||
(content (plist-get state :content-lines))
|
||||
(snapshot (ebox-surface-buffer-snapshot (current-buffer)))
|
||||
(revision (ebox-surface-buffer-revision (current-buffer)))
|
||||
(before (buffer-string)))
|
||||
(mounts (ebox-layer-publication-test--mount-signature (current-buffer)))
|
||||
(before (buffer-string))
|
||||
(owned (plist-get runtime :render-owned-text-values))
|
||||
(membership
|
||||
(let (properties)
|
||||
(maphash
|
||||
(lambda (property values)
|
||||
(let (entries)
|
||||
(maphash (lambda (value member) (push (cons value member) entries))
|
||||
values)
|
||||
(push (list property values entries) properties)))
|
||||
owned)
|
||||
properties))
|
||||
rejected-step)
|
||||
(ebox-layer-scroll-test--without-general-preparation
|
||||
(let ((tp--surface-publication-step-function
|
||||
(lambda (step _surface)
|
||||
(when (eq step failure-step) (error "Reject cached root scroll")))))
|
||||
(should-error (ebox--scroll-region-by region-id 1 1)))
|
||||
(when (eq step failure-step)
|
||||
(setq rejected-step step)
|
||||
(error "Reject cached root scroll")))))
|
||||
(should-error (ebox--scroll-region-by region-id 1 1))))
|
||||
(should (eq rejected-step failure-step))
|
||||
(should (eq runtime (ebox--buffer-render-state (current-buffer))))
|
||||
(should (eq owned (plist-get runtime :render-owned-text-values)))
|
||||
(should (= (hash-table-count owned) (length membership)))
|
||||
(dolist (property membership)
|
||||
(let ((values (gethash (car property) owned))
|
||||
(entries (nth 2 property)))
|
||||
(should (eq values (nth 1 property)))
|
||||
(should (= (hash-table-count values) (length entries)))
|
||||
(dolist (entry entries)
|
||||
(should (eq (gethash (car entry) values 'missing) (cdr entry))))))
|
||||
(should (eq state (ebox-layer-scroll-test--root-state)))
|
||||
(should (eq content (plist-get state :content-lines)))
|
||||
(let* ((after (ebox-surface-buffer-snapshot (current-buffer)))
|
||||
@ -210,8 +306,85 @@
|
||||
(should same-input)
|
||||
(should (= (plist-get snapshot :mount-id) (plist-get after :mount-id))))
|
||||
(should (= revision (ebox-surface-buffer-revision (current-buffer))))
|
||||
(should (equal mounts
|
||||
(ebox-layer-publication-test--mount-signature (current-buffer))))
|
||||
(should (equal-including-properties before (buffer-string)))
|
||||
(should (= 1 (ebox--scroll-region-by region-id 1 1)))
|
||||
(ebox-layer-scroll-test--without-general-preparation
|
||||
(should (= 1 (ebox--scroll-region-by region-id 1 1))))
|
||||
(should (= (1+ revision) (ebox-surface-buffer-revision (current-buffer))))
|
||||
(ebox-layer-scroll-test--assert-fresh)))))
|
||||
|
||||
(ert-deftest ebox-layer-scroll-reactive-width-change-renders-current-viewport ()
|
||||
"A retained cached-scroll producer republishes current width without replay."
|
||||
(ebox-layer-scroll-test--with-buffer
|
||||
(ebox-render-to-buffer (current-buffer) (ebox-layer-scroll-test--input 'fractional))
|
||||
(ebox-region-update "root" :width '(vw 100))
|
||||
(let* ((region (plist-get (plist-get (ebox-layer-scroll-test--root-state) :box)
|
||||
:region-id))
|
||||
(width (ebox-surface--signals-viewport-width ebox-surface--context-signals))
|
||||
(publish (symbol-function 'ebox-surface--scroll-viewport-result))
|
||||
(prepared-publications 0))
|
||||
(cl-letf (((symbol-function 'ebox-surface--scroll-viewport-result)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf prepared-publications)
|
||||
(apply publish arguments))))
|
||||
(ebox-layer-scroll-test--without-general-preparation
|
||||
(should (= 1 (ebox--scroll-region-by region 1 1))))
|
||||
(should (= prepared-publications 1))
|
||||
(let ((revision (ebox-surface-buffer-revision (current-buffer)))
|
||||
(before (buffer-string)))
|
||||
(should (= (tp-signal-subscriber-count width) 1))
|
||||
(tp-with-transaction (tp-signal-set width 30))
|
||||
(should (= (1+ revision) (ebox-surface-buffer-revision (current-buffer))))
|
||||
(should (= (plist-get (ebox--buffer-render-state (current-buffer))
|
||||
:viewport-width) 30))
|
||||
(should (= (plist-get (ebox-layer-scroll-test--root-state) :scroll-offset) 1))
|
||||
(should-not (equal-including-properties before (buffer-string)))
|
||||
(should (= prepared-publications 1))
|
||||
(ebox-layer-scroll-test--assert-fresh))))))
|
||||
|
||||
(ert-deftest ebox-layer-scroll-first-guard-miss-releases-batch-and-uses-current-axes ()
|
||||
"A declined prepared producer releases its candidate and follows current axes."
|
||||
(ebox-layer-scroll-test--with-buffer
|
||||
(ebox-render-to-buffer (current-buffer) (ebox-layer-scroll-test--input 'fractional))
|
||||
(ebox-region-update "root" :width '(vw 100))
|
||||
(let* ((region (plist-get (plist-get (ebox-layer-scroll-test--root-state) :box)
|
||||
:region-id))
|
||||
(surface ebox-surface--buffer-surface)
|
||||
(signals ebox-surface--context-signals)
|
||||
(width (ebox-surface--signals-viewport-width signals))
|
||||
(make-producer (symbol-function 'ebox-surface--scroll-viewport-producer))
|
||||
(retired (make-hash-table :test #'eq :weakness 'key))
|
||||
retained-producer)
|
||||
(cl-letf (((symbol-function 'ebox-surface--scroll-viewport-producer)
|
||||
(lambda (previous prepared context-signals)
|
||||
(let ((declined (copy-sequence prepared)))
|
||||
;; Invalidate only the captured certificate, before TP's
|
||||
;; first evaluation, leaving the live display unchanged.
|
||||
(plist-put declined :display-signature 'stale-display)
|
||||
(puthash declined t retired)
|
||||
(puthash (plist-get declined :scroll-viewport-batch) t retired)
|
||||
(setq retained-producer
|
||||
(funcall make-producer previous declined context-signals)))))
|
||||
((symbol-function 'ebox-surface--scroll-viewport-result)
|
||||
(lambda (&rest _) (ert-fail "Replayed a declined viewport batch"))))
|
||||
(should (= 1 (ebox--scroll-region-by region 1 1))))
|
||||
(should retained-producer)
|
||||
(ebox-layer-scroll-test--assert-fresh)
|
||||
(garbage-collect)
|
||||
(should (= (hash-table-count retired) 0))
|
||||
(ebox-region-update "root" :width '(ch 20))
|
||||
(should (= (tp-signal-subscriber-count width) 0))
|
||||
(let ((revision (ebox-surface-buffer-revision (current-buffer))))
|
||||
(cl-letf (((symbol-function 'ebox-surface--scroll-viewport-result)
|
||||
(lambda (&rest _) (ert-fail "Replayed a retired viewport batch"))))
|
||||
(tp-surface-update surface retained-producer))
|
||||
(should (= (1+ revision) (ebox-surface-buffer-revision (current-buffer))))
|
||||
(should (= (tp-signal-subscriber-count width) 0))
|
||||
(should (= (tp-signal-subscriber-count
|
||||
(ebox-surface--signals-display signals)) 1))
|
||||
(should (= (tp-signal-subscriber-count
|
||||
(ebox-surface--signals-scroll signals)) 1))
|
||||
(ebox-layer-scroll-test--assert-fresh)))))
|
||||
|
||||
(ert-deftest ebox-layer-scroll-visible-root-dependencies-retain-composed-cache ()
|
||||
@ -322,11 +495,7 @@
|
||||
(ebox-layer-scroll-test--with-buffer
|
||||
(ebox-render-to-buffer
|
||||
(current-buffer)
|
||||
(ebox-build
|
||||
`(column :id "root" :width (vw 100) :height (vh 100) :overflow scroll
|
||||
,@(cl-loop for index below 80
|
||||
collect `(box :padding-left (px 1.5)
|
||||
,(format "ROW%02d" index))))))
|
||||
(ebox-layer-scroll-test--ordinary-input))
|
||||
(let* ((runtime (ebox--buffer-render-state (current-buffer)))
|
||||
(state (ebox-layer-scroll-test--root-state))
|
||||
(region-id (plist-get (plist-get state :box) :region-id)))
|
||||
@ -339,10 +508,25 @@
|
||||
(should (= 1 (ebox--scroll-region-by region-id 1 1)))
|
||||
(should (= -1 (ebox--scroll-region-by region-id -1 1)))
|
||||
(should (plist-get (ebox-layer-scroll-test--root-state) :rendered-content-lines))
|
||||
(dolist (delta '(1 1 -1 -1))
|
||||
(let ((revision (ebox-surface-buffer-revision (current-buffer)))
|
||||
(before (buffer-string))
|
||||
(objects (copy-hash-table
|
||||
(plist-get (ebox--buffer-render-state (current-buffer))
|
||||
:surface-node-object-table))))
|
||||
(ebox-layer-scroll-test--without-general-preparation
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate-node)
|
||||
(lambda (&rest _) (ert-fail "Cached ordinary scroll rendered wrapper"))))
|
||||
(should (= 1 (ebox--scroll-region-by region-id 1 1))))
|
||||
(ebox-layer-scroll-test--assert-fresh))))
|
||||
(should (= delta (ebox--scroll-region-by region-id delta 1)))))
|
||||
(should (= (1+ revision) (ebox-surface-buffer-revision (current-buffer))))
|
||||
(should-not (equal-including-properties before (buffer-string)))
|
||||
(maphash
|
||||
(lambda (id object)
|
||||
(should (eq object
|
||||
(gethash id (plist-get (ebox--buffer-render-state (current-buffer))
|
||||
:surface-node-object-table)))))
|
||||
objects)
|
||||
(ebox-layer-scroll-test--assert-fresh))))))
|
||||
|
||||
(provide 'ebox-layer-scroll-tests)
|
||||
;;; ebox-layer-scroll-tests.el ends here
|
||||
|
||||
@ -387,6 +387,11 @@
|
||||
:proposed-category disposable-cache :owner ebox-incremental :authority ebox
|
||||
:lifetime buffer-runtime :rollback copy-on-candidate
|
||||
:rebuild viewport-proof :cleanup clear-cache-or-buffer-kill)
|
||||
(:id buffer-runtime/scroll-viewport-owner-cache
|
||||
:storage (:state-key :scroll-viewport-owner-cache)
|
||||
:proposed-category disposable-cache :owner ebox-surface :authority ebox
|
||||
:lifetime unchanged-topology-and-object-indexes :rollback copy-on-candidate
|
||||
:rebuild rich-line-owner-resolution :cleanup ordinary-projection)
|
||||
(:id buffer-runtime/render-owned-text-values
|
||||
:storage (:state-key :render-owned-text-values)
|
||||
:proposed-category generation-bound-mutable :owner ebox-surface :authority ebox
|
||||
|
||||
@ -10,6 +10,26 @@
|
||||
(require 'cl-lib)
|
||||
(require 'ebox)
|
||||
|
||||
(defmacro ebox-nested-scroll-test--without-general-preparation (&rest body)
|
||||
"Run cached publication BODY without document preparation or surface scans."
|
||||
(declare (indent 0) (debug t))
|
||||
`(let ((stages '(ebox-incremental-prepare-scoped-commit
|
||||
ebox-incremental--prepare-declarative-runtime
|
||||
ebox--runtime-index ebox-surface--surface-plan))
|
||||
counters advices)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(dolist (stage stages)
|
||||
(let* ((counter (cons stage 0))
|
||||
(advice (lambda (&rest _) (cl-incf (cdr counter)))))
|
||||
(push counter counters)
|
||||
(push (cons stage advice) advices)
|
||||
(advice-add stage :before advice)))
|
||||
,@body)
|
||||
(dolist (entry advices) (advice-remove (car entry) (cdr entry))))
|
||||
(should (equal (nreverse counters)
|
||||
(mapcar (lambda (stage) (cons stage 0)) stages)))))
|
||||
|
||||
(defmacro ebox-nested-scroll-test--with-buffer (&rest body)
|
||||
"Run BODY with an isolated mounted surface and the Elisp renderer."
|
||||
(declare (indent 0) (debug t))
|
||||
@ -87,7 +107,7 @@ PRESERVE-ORDER-P retains attachment order for exact rollback checks."
|
||||
(should (equal (substring-no-properties actual)
|
||||
(buffer-substring-no-properties (point-min) (point-max))))
|
||||
(dotimes (position (length actual))
|
||||
(dolist (property '(face display help-echo mouse-face pointer keymap))
|
||||
(dolist (property '(face font-lock-face display help-echo mouse-face pointer keymap))
|
||||
(should (equal (get-text-property position property actual)
|
||||
(get-text-property (1+ position) property)))))
|
||||
(should (equal mounts (ebox-nested-scroll-test--mounts))))
|
||||
@ -193,10 +213,28 @@ PRESERVE-ORDER-P retains attachment order for exact rollback checks."
|
||||
(ebox-nested-scroll-test--parity)
|
||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||
(scroll (gethash right (plist-get state :scroll-state-table)))
|
||||
(lines (plist-get scroll :content-lines)))
|
||||
(lines (plist-get scroll :content-lines))
|
||||
(revision (ebox-surface-buffer-revision (current-buffer)))
|
||||
(objects (copy-hash-table (plist-get state :surface-node-object-table))))
|
||||
(dolist (delta '(1 1 -1))
|
||||
(ebox-nested-scroll-test--without-general-preparation
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (&rest _) (ert-fail "Independent scroll rendered root"))))
|
||||
(should (= (ebox--surface-scroll-region-by (current-buffer) left 1 nil) 1)))
|
||||
(should (= (ebox--surface-scroll-region-by
|
||||
(current-buffer) left delta nil) delta))))
|
||||
(cl-incf revision)
|
||||
(should (= revision (ebox-surface-buffer-revision (current-buffer))))
|
||||
(let ((next (gethash right (plist-get (ebox--buffer-render-state
|
||||
(current-buffer)) :scroll-state-table))))
|
||||
(should (= (plist-get next :scroll-offset) 2))
|
||||
(should (eq (plist-get next :content-lines) lines)))
|
||||
(ebox-nested-scroll-test--parity))
|
||||
(maphash
|
||||
(lambda (id object)
|
||||
(should (eq object
|
||||
(gethash id (plist-get (ebox--buffer-render-state (current-buffer))
|
||||
:surface-node-object-table)))))
|
||||
objects)
|
||||
(let ((next (gethash right (plist-get (ebox--buffer-render-state
|
||||
(current-buffer)) :scroll-state-table))))
|
||||
(should (= (plist-get next :scroll-offset) 2))
|
||||
@ -204,10 +242,14 @@ PRESERVE-ORDER-P retains attachment order for exact rollback checks."
|
||||
(ebox-nested-scroll-test--parity)
|
||||
(ebox-region-update "line-1" :content "UPDATED")
|
||||
(should (string-match-p "UPDATED" (buffer-string)))
|
||||
(should (= (plist-get (ebox--scroll-get-state right) :scroll-offset) 2))
|
||||
(ebox-nested-scroll-test--parity)
|
||||
(ebox-rerender-buffer-with-context (current-buffer) 120 35)
|
||||
(should (= (plist-get (ebox--scroll-get-state left) :scroll-offset) 1))
|
||||
(should (= (plist-get (ebox--scroll-get-state right) :scroll-offset) 2))
|
||||
(ebox-nested-scroll-test--parity)
|
||||
(should (= (ebox--surface-scroll-region-by (current-buffer) left -1 nil) -1))
|
||||
(ebox-nested-scroll-test--without-general-preparation
|
||||
(should (= (ebox--surface-scroll-region-by (current-buffer) left -1 nil) -1)))
|
||||
(ebox-nested-scroll-test--parity))))
|
||||
|
||||
(ert-deftest ebox-nested-scroll-rejection-restores-local-and-fallback-state ()
|
||||
@ -224,6 +266,7 @@ PRESERVE-ORDER-P retains attachment order for exact rollback checks."
|
||||
(before (buffer-string))
|
||||
(mounts (ebox-nested-scroll-test--mounts t))
|
||||
(revision (ebox-surface-buffer-revision (current-buffer)))
|
||||
rejected-step
|
||||
(root-renders 0)
|
||||
(render (symbol-function 'ebox-surface--render-candidate)))
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
@ -231,9 +274,16 @@ PRESERVE-ORDER-P retains attachment order for exact rollback checks."
|
||||
(cl-incf root-renders) (apply render arguments))))
|
||||
(let ((tp--surface-publication-step-function
|
||||
(lambda (step _surface)
|
||||
(when (eq step 'client-state) (error "Reject nested scroll")))))
|
||||
(when (eq step 'client-state)
|
||||
(setq rejected-step step)
|
||||
(error "Reject nested scroll"))))
|
||||
(attempt (lambda ()
|
||||
(should-error (ebox--surface-scroll-region-by
|
||||
(current-buffer) region 1 nil))))
|
||||
(current-buffer) region 1 nil)))))
|
||||
(if fallback (funcall attempt)
|
||||
(ebox-nested-scroll-test--without-general-preparation
|
||||
(funcall attempt)))))
|
||||
(should (eq rejected-step 'client-state))
|
||||
(should (eq state (ebox--buffer-render-state (current-buffer))))
|
||||
(should (= revision (ebox-surface-buffer-revision (current-buffer))))
|
||||
(should (equal-including-properties before (buffer-string)))
|
||||
@ -241,7 +291,11 @@ PRESERVE-ORDER-P retains attachment order for exact rollback checks."
|
||||
(should (= (plist-get scroll :scroll-offset) 0))
|
||||
(should (= (or (ebox-get box :scroll-offset) 0) 0))
|
||||
(should (if fallback (> root-renders 0) (= root-renders 0)))
|
||||
(if fallback
|
||||
(should (= (ebox--surface-scroll-region-by (current-buffer) region 1 nil) 1))
|
||||
(ebox-nested-scroll-test--without-general-preparation
|
||||
(should (= (ebox--surface-scroll-region-by (current-buffer) region 1 nil) 1))))
|
||||
(should (= (1+ revision) (ebox-surface-buffer-revision (current-buffer))))
|
||||
(ebox-nested-scroll-test--parity)))))
|
||||
|
||||
(ert-deftest ebox-nested-scroll-updates-enclosing-cache-and-role-membership ()
|
||||
@ -253,12 +307,14 @@ PRESERVE-ORDER-P retains attachment order for exact rollback checks."
|
||||
(outer (ebox-nested-scroll-test--region "root"))
|
||||
(hidden (ebox-nested-scroll-test--region "line-0"))
|
||||
(visible (ebox-nested-scroll-test--region "line-5")))
|
||||
(ebox-nested-scroll-test--without-general-preparation
|
||||
(cl-letf (((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (&rest _) (ert-fail "Inner scroll rendered enclosing root"))))
|
||||
(should (= (ebox--surface-scroll-region-by (current-buffer) inner 1 nil) 1)))
|
||||
(should (= (ebox--surface-scroll-region-by (current-buffer) inner 1 nil) 1))))
|
||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||
(scroll (gethash outer (plist-get state :scroll-state-table)))
|
||||
(members (ebox--scroll-state-ensure-content-region-id-set scroll)))
|
||||
(should (= (plist-get scroll :scroll-offset) 0))
|
||||
(should (eq (plist-get scroll :box) (plist-get state :root-node)))
|
||||
(should-not (gethash hidden members))
|
||||
(should (gethash visible members))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user