Complete retained layer update and packaging contracts
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:
Kinneyzhang 2026-09-10 03:00:05 +08:00
parent 4a25d573c2
commit 2a64573d42
11 changed files with 135 additions and 3 deletions

View File

@ -190,7 +190,7 @@ the fact once for its spatial root.")
(car matches)))
(defun ebox-layer--absolute-origin (node host bounds width height layer-width layer-height)
"Resolve NODE's origin in HOST's BASE, or nil for an invisible anchor.
"Resolve NODE's origin in HOST, or nil for an invisible anchor.
BOUNDS is the already resolved anchor rectangle, or nil.
WIDTH and HEIGHT bound the host; LAYER-WIDTH and LAYER-HEIGHT bound the panel."
(let ((x (ebox-layer--offset node :left host width))

View File

@ -1570,8 +1570,10 @@ the separate unwrapped measurement keeps that resolution non-recursive."
(cond
((and (eq (ebox-get props :width) 'max-content)
(not (memq (ebox--box-layout-kind props) '(normal column))))
;; This is an unbounded *visible* formatting context, not necessarily
;; an intrinsic-size probe. Preserve the caller's measurement mode;
;; suppressing paint here would discard positioned descendants.
(let ((ebox-viewport-width nil)
(ebox--intrinsic-layout-measurement t)
(ebox--inline-auto-width-intrinsic-p nil))
(funcall function)))
(content-viewport

View File

@ -18,6 +18,8 @@
(require 'ebox-runtime-index)
(require 'ebox-size)
(defvar ebox-incremental--render-portals-p)
(declare-function ebox-string-height "ebox" (string))
(declare-function ebox-surface--retained-property-value-equal-p
"ebox-surface" (left right))

View File

@ -9,6 +9,10 @@
(require 'cl-lib)
(defvar ebox-layer--active-p)
(defvar ebox-incremental--render-portals-p)
(defvar ebox--render-source-index)
(declare-function ebox-interaction--refresh-hover! "ebox-interaction"
(string start end &optional face))
@ -266,6 +270,16 @@ including strings returned from a render-cache hit."
(ebox--css-size-context-active-p t)
(ebox--render-runtime-revision
(plist-get ,render-state :runtime-revision))
(ebox--render-source-index
(plist-get ,render-state :source-index))
(ebox-layer--active-p
(if (plist-member ,render-state :layered-p)
(plist-get ,render-state :layered-p)
'unknown))
(ebox-incremental--render-portals-p
(if (plist-member ,render-state :root-portals-p)
(plist-get ,render-state :root-portals-p)
'unknown))
(ebox--surface-materialization-active t)
(ebox--render-cache-table
(plist-get ,render-state :render-cache))

View File

@ -301,6 +301,9 @@ rather than relying on an implicit default.")
(:symbol ebox--box-content-empty-width
:reason dynamic-scalar-result
:evidence numeric-inline-extent-returned-with-one-content-materialization)
(:symbol ebox-layer--portals
:reason dynamically-bound-render-collection
:evidence let-bound-per-spatial-root-and-discarded-with-the-candidate-render)
(:symbol ebox--string-pixel-width-cache-ring-index
:reason numeric-eviction-cursor
:evidence value-is-an-integer-ring-position)

View File

@ -24,6 +24,7 @@
(require 'tp)
(defvar ebox-region-types)
(declare-function ebox--runtime-node-ids "ebox" (node))
(defvar ebox--region-id-counter)
(defvar ebox--runtime-node-id-counter)
(defvar ebox--region-box-table)

View File

@ -3854,6 +3854,8 @@ Defaults to the current buffer."
:text-align :vertical-align
:overflow :wrap-mode
:scroll-offset :visibility)
(cl-loop for (property _value) on ebox-style--box-runtime-defaults by #'cddr
collect property)
ebox-style--item-projection-properties)
"Longhand box properties accepted by `ebox-region-update'.")

View File

@ -42,6 +42,27 @@
(plist-get (ebox-surface-buffer-snapshot (current-buffer))
:input))))))
(ert-deftest ebox-layer-publication-updates-every-placement-property ()
"Each layer declaration reaches the mounted runtime through a region update."
(dolist (properties '((:position relative) (:left (ch 2)) (:top (lh 1))
(:z-index -1) (:layer root) (:anchor "lower")
(:placement top-end)))
(ert-info ((format "Update %S" properties))
(ebox-layer-publication-test--with-buffer
(ebox-render-to-buffer
(current-buffer) (ebox-layer-publication-test--input nil t))
(let* ((handle (ebox-region-resolve (current-buffer) "upper"))
(region-id (cdr (ebox-selector--region-target handle)))
(revision (ebox-surface-buffer-revision (current-buffer))))
(should (apply #'ebox-region-update handle properties))
(should (> (ebox-surface-buffer-revision (current-buffer)) revision))
(should (equal (plist-get
(ebox--buffer-region-render-owner-node
(current-buffer) region-id)
(car properties))
(cadr properties)))
(ebox-layer-publication-test--assert-fresh-render))))))
(ert-deftest ebox-layer-publication-hidden-updates-reveal-current-input ()
"Hidden content and paint updates survive without painting over upper text."
(ebox-layer-publication-test--with-buffer

View File

@ -77,5 +77,88 @@
:width (ch 6) "HIDDEN")))))
(should (equal (substring-no-properties output) "LATEST"))))
(ert-deftest ebox-layer-max-content-row-paints-positioned-descendants ()
(let ((output (ebox-layer-test--render
'(row :width max-content
(box :width (ch 4) :height (lh 1)
(box "ABCD")
(box :position absolute :left (ch 1)
:width (ch 2) "XX"))))))
(should (equal (substring-no-properties output) "AXXD"))))
(ert-deftest ebox-layer-anchor-dependency-resolves-before-paint-order ()
(let ((output (ebox-layer-test--render
'(box :width (ch 10) :height (lh 4)
(box "0123456789\nabcdefghij\nABCDEFGHIJ\n0123456789")
(box :position absolute :anchor parent-panel :width (ch 4)
:z-index 2 "MENU")
(box :id parent-panel :position absolute :left (ch 2)
:top (lh 1) :width (ch 4) :z-index 1 "BASE")))))
(should (equal (substring-no-properties output)
"0123456789\nabBASEghij\nABMENUGHIJ\n0123456789"))))
(ert-deftest ebox-layer-anchor-cycles-are-rejected ()
(should-error
(ebox-layer-test--render
'(box :width (ch 10) :height (lh 4)
(box :id a :position absolute :anchor b "A")
(box :id b :position absolute :anchor a "B")))))
(ert-deftest ebox-layer-absolute-margins-do-not-cover-underlay ()
(let ((output (ebox-layer-test--render
'(box :width (ch 8) :height (lh 3)
(box "abcdefgh\nABCDEFGH\n01234567")
(box :position absolute :width (ch 4) :height (lh 1)
:margin ((lh 1) (ch 1)) :background-color "red"
"TOP!")))))
(should (equal (substring-no-properties output)
"abcdefgh\nATOP!FGH\n01234567"))))
(ert-deftest ebox-layer-absolute-keeps-all-flow-layout-sizes ()
(dolist (tag '(box row column flex grid))
(let* ((form `(,tag :width (ch 8)
(box :width (ch 8) :height (lh 1) "abcdefgh")
(box :position absolute :left (ch 2)
:width (ch 2) :height (lh 9) "XX")))
(output (ebox-layer-test--render form)))
(ert-info ((format "Layout %s" tag))
(should (= (length (ebox-string-lines output)) 1))
(should (equal (substring-no-properties output) "abXXefgh"))))))
(ert-deftest ebox-layer-root-projection-inside-an-absolute-panel ()
(let ((output (ebox-layer-test--render
'(box :width (ch 10) :height (lh 4)
(box "0123456789\nabcdefghij\nABCDEFGHIJ\n0123456789")
(box :position absolute :left (ch 2) :top (lh 1)
:width (ch 4) :height (lh 1)
(box :id trigger "BASE")
(box :position absolute :layer root :anchor trigger
:width (ch 4) :height (lh 1) "MENU"))))))
(should (equal (substring-no-properties output)
"0123456789\nabBASEghij\nABMENUGHIJ\n0123456789"))))
(ert-deftest ebox-layer-native-to-layer-transition-keeps-composition ()
(skip-unless (and (fboundp 'ebox-native-reflow-layout-ready-p)
(ebox-native-reflow-layout-ready-p)))
(let ((ebox-viewport-width 80) (ebox-viewport-height 10)
(ebox-runtime-idle-prewarm nil)
(ebox-runtime-idle-reflow-cache-prewarm nil))
(with-temp-buffer
(ebox-render-to-buffer
(current-buffer)
(ebox-build '(column :width (px 8) :height (lh 2)
(box :id "lower" :width (px 8) :height (lh 1) "LOWER001")
(box :id "upper" :width (px 8) :height (lh 1) "UPPER001"))))
(should (eq (plist-get (ebox--buffer-render-state (current-buffer))
:projection-kind) 'native-frame))
(should (ebox-region-update "upper" :position 'absolute))
(should (plist-get (ebox--buffer-render-state (current-buffer)) :layered-p))
(should-not (string-match-p "LOWER001" (buffer-string)))
(should (string-match-p "UPPER001" (buffer-string)))
(ebox-region-update "lower" :content "LOWER002")
(ebox-region-update "upper" :visibility 'hidden)
(should (string-match-p "LOWER002" (buffer-string)))
(should-not (string-match-p "UPPER001" (buffer-string))))))
(provide 'ebox-layer-tests)
;;; ebox-layer-tests.el ends here

View File

@ -25,6 +25,9 @@
(:storage (:state-key :content-index)
:why address-scalar-not-a-store :owner ebox-surface
:evidence plist-key-holds-a-numeric-content-address)
(:storage (:state-key :z-index)
:why paint-order-scalar-not-a-store :owner ebox-layer
:evidence style-schema-requires-an-integer-used-only-for-layer-ordering)
(:storage (:state-key :max-jobs)
:why native-limit-not-a-store :owner ebox-native-reflow
:evidence plist-key-holds-a-numeric-capacity)

View File

@ -162,7 +162,8 @@
"ebox-node-factory.el"
"ebox-child-range.el"
"ebox-tree.el" "ebox-measure.el"
"ebox-fragment.el" "ebox-render-context.el" "ebox-layout.el"
"ebox-fragment.el" "ebox-render-context.el" "ebox-composite.el"
"ebox-layer.el" "ebox-layout.el"
"ebox-flex.el" "ebox-grid.el" "ebox-canonical.el"
"ebox-buffer-backend.el" "ebox-patch-plan.el"
"ebox-incremental.el"