Repository navigation
fix: forwarded slots in vector output, href allowlist in the background, byte-cut file name, calc() radius, fixed-element box - #8
Merged
Conversation
…slot The flat-tree walk flattened nested slots, so the slot a forwarded node is assigned to (its flatParent) never got a box: the node had no parent in the scene, its text was drawn transparent and a slotted element's subtree was never painted. The walk now yields the forwarding slot itself and takes an empty slot's fallback children explicitly. Red before the fix: vector-boxes.html (fill of "forwarded text" #000, diff 2.6 %), the closed-tree case in vector.spec.ts (paint null); the fallback line in shadow.html goes red when the length check is mutated.
…http(s) or mailto URL messages.ts accepted any string as a run's or link area's href and build.ts emitted it as <a href>; linkFor's scheme allowlist ran only in the content script, which messages.ts itself treats as hostile. isKeptHref (links.ts) now applies the same allowlist in the validator. Red before the fix: messages.test.ts rows "run href javascript:" and "link href relative"; links.test.ts isKeptHref.
A CJK or emoji title could reach 360 bytes, past the 255-byte name limit file systems and Firefox's download enforce (folder.ts records the folder case). The cut now counts UTF-8 bytes per code point; ASCII names are unchanged. Red before the fix: filename.test.ts, 207 bytes for an emoji title.
… instead of a square shape
Mixed units (10px / calc(50% - 2px)) stay a calc() in the computed value,
which parseFloat read as 0: background, border and content clip came out
square. Such a box is now pixels (reason "border"), checked on the whole
value so a two-value radius with a plain first number is caught too.
Red before the fix: vector.spec.ts "boxes: a radius the engine keeps as
calc()" (unsupported {} in both engines; the test asserts the premise
that the computed value keeps the parenthesis).
…region, while the page scrolls The overlay stored the element's document rect once at selection and only redrew it on scroll, so a fixed header drifted away from its box and Save took the document region the element had left. The rect is read anew on every scroll while an element is selected. Red before the fix: overlay.spec.ts "a selected fixed element keeps its box on screen while the page scrolls" (Firefox: box at client y 170 instead of 670; Chromium: saved rect y 670 instead of 1170). The test waits for the scroll event itself: Chromium fires it a frame after scrollTo, and Enter before it would save the rect of before.
…nders; attribute whitespace survives - flag.ts: one timer per tab, a new notice cancels the previous one (before, the first timer cleared a notice shown 2.5 s later after 0.5 s). - build.ts: the line separator's x is found by a loop, not Math.max(...spread), which threw RangeError at 200 000 runs (measured). - xml.ts: xmlAttr writes tab, newline and carriage return as character references; a parser normalizes the literal ones to spaces. Red before the fix: flag.test.ts (second notice cleared early), svg-build.test.ts (RangeError), xml.test.ts (references). Golden unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
A review of the whole tree turned up six defects worth fixing now: one in the vector picture (text and boxes slotted through a forwarding slot vanished), one against the background's own threat model (
hrefwas trusted), and four edge cases. Each fix is the smallest change at the root cause, one commit each, each pinned by a test that failed before it.Claims
walkFlatTreeyields the forwarding<slot>instead of flattening it, so the node'sflatParenthas aBoxinscene.ts; an empty slot still walks its fallback childrensavewhose run or link-areahrefis not an absolutehttp:,https:ormailto:URL (isKeptHrefinlinks.ts, applied inmessages.ts), so a forged message cannot put ajavascript:link into the saved<a href>filename.ts)border-radiuswhose computed value stayscalc()/min()/max()/clamp()(mixed units, e.g.10px / calc(50% - 2px)) makes the box aborderpatch instead of a square shape — before,parseFloatread it as 0 (scene.ts#reasonOf)position: fixedorstickyelement keeps its box on screen while the page scrolls, and Save takes the region where the element is at that moment (overlay.tsonScrollre-readsdocRectOf); the same handler serves inner scroll containers andresize, so a selected element follows those tooflag.ts)lineSeparatorno longer spreads every run intoMath.max(build.ts)xmlAttrwrites tab, newline and carriage return as	 , so an attribute value survives XML attribute-value normalization unchanged (xml.ts)Verification
The three Firefox glue failures (
item 12: metadata,full-viewport selection at DPR 2,vector output, same pages) see a 1265 px viewport: this machine shows classic scrollbars (a mouse is attached), which takes 15 px off the window. The same three fail onmainfor the same reason; no change here touches the viewport.Tests
tests/fixtures/vector-boxes.html:<snap-outer>→<snap-inner>forwarding, green "forwarded text" (fills) and a red "forwarded box";tests/fixtures/shadow.html: a slot with fallback text;tests/layout/vector.spec.ts: the same forwarding through two closed trees (paint of the run), andboxes: a radius the engine keeps as calc()(asserts the premise that the computed value keeps the parenthesis, thenunsupported: {border: 1})tests/unit/messages.test.ts: rowsrun href javascript:andlink href relative;tests/unit/links.test.ts:isKeptHreftests/unit/filename.test.ts: byte length for"日本語"×60and"ä"×300tests/fixtures/overlay.html+tests/layout/overlay.spec.ts: a fixed footer selected, page scrolled by 500 px, box still on it, saved rect moved by 500 px (the test waits for the scroll event itself: Chromium fires it a frame afterscrollTo)tests/unit/flag.test.ts: two notices, the first timer must not clear the second;tests/unit/svg-build.test.ts: 200 000 runs on one line;tests/unit/xml.test.ts: the three referencesmainwithout this change: yes — each test was run red first (fill#000and 2.6 % pixel diff for the forwarding fixture,paint nullin the closed-tree case,unsupported {}for the calc radius, box at client y 170 / saved y 670 for the fixed footer,207 bytesfor the emoji title,RangeError: Maximum call stack size exceededfor the 200k runs). Mutants per condition operand were run too:assigned.length >= 0(fallback line goes red),isHrefwithout thenullbranch andisKeptHrefreturning true, the 2-byte count set to 1, the radius check inverted (every px-radius fixture becomes a patch), the rect refresh replaced by a self-assignment.Public contracts
<image>tiles under an invisible text layer — one<text>withtextLengthper run,fill-opacity="0", OCR words in<g id="ocr">, linked runs wrapped in<a href><snapii:capture>keepsschema: 1and every field ofcaptureRecord()insrc/shared/svg/metadata.ts(no field removed, renamed or retyped); the Dublin Core fields stay as they areactiveTab,scripting,downloads,clipboardWrite,storage— nohost_permissions, nocontent_scripts(decision D1, docs/development.md)storage.synckeep their keys, types and meaning (Settingsinsrc/shared/types.ts); a value stored by an older version still loadssnapii@qmmq.dedata_collection_permissionsstaysnone, the saved file reveals nothing new about the userdist/— orNOTICEand the license texts are updated with itSaved-file impact
tests/unit/golden/simple.svgchanged: no — byte-identical (the golden test passes). Bytes of a real save change only where a title was cut (non-ASCII titles get shorter), where an attribute carried a tab/newline/CR (now a reference, same value after parsing), and in vector output for the two fixed defects (forwarded content present, calc-radius boxes as patches).Risk and rollback
INVALID_MODEL) —linkForonly ever emits absolute URLs of the three kept schemes, whichisKeptHrefaccepts; a forwarding slot that the walk now yields counts one more element towardsMAX_ELEMENTSOut of scope
allFramesinjection — a design question under D1)trimToClipincollect.ts(to be measured first on a 1 MB single-line<pre>)#extent, OCR message size vs. the measured limit, a staleanchorSourcesfilter order — each tracked separatelyAssistance