NIFI-16025 Canvas SVG transform overflow guards#11352
Open
rfellows wants to merge 1 commit into
Open
Conversation
Prevent browser SVG errors and frozen navigation caused by extreme finite floating-point coordinate values (e.g. 7.49e+307) stored in NiFi flow definitions. The root cause is that Number.isFinite() accepts arbitrarily large finite numbers that overflow float32 rendering and D3 arithmetic; this change adds a layered defence of magnitude- and range-bounded guards throughout the frontend canvas pipeline. ## Shared helpers (libs/shared) Add canvas-bounds.util.ts with: - MAX_ABS_COORD (1e9) and MAX_ABS_TRANSLATE (1e12) magnitude bounds - MIN_SCALE (0.2) / MAX_SCALE (8) scale range, centralising the values previously hardcoded in canvas-view.service.ts - isFiniteInBound(value, bound) — type guard combining Number.isFinite and Math.abs check - isScaleInBound(value) — rejects near-zero and out-of-range scales - clampScale(scale, fallback?) — NaN-safe clamp - sanitizePosition(position, opts) — clamps bad coordinates to (0, 0) with a warn-once, per-component-id console warning Re-export from services/index.ts. Full spec coverage (30 tests) including the ~7.49e+307 fingerprint and the warn-once dedupe. ## Data-ingestion sanitization flow.effects.ts (loadProcessGroup$): walk all positioned entity collections (processors, process groups, remote process groups, input/output ports, labels, funnels) and connection bends through sanitizePosition before the NgRx action is dispatched. A session-scoped warnedPositionIds Set prevents console spam during polling refreshes. connector-canvas.effects.ts (loadConnectorFlow$): same sanitizer applied to all collections. Even the read-only connector view uses the reusable canvas, so its inputs must be clean. ## Flow-designer transform guards canvas-view.service.ts: - Zoom handler: replace !isNaN checks with isFiniteInBound + isScaleInBound - transform(): early-return guard before behavior.transform() call - getSelectionBoundingClientRect(): return null on empty selection; swap Number.MAX_VALUE / MIN_VALUE sentinel seeds for POSITIVE/NEGATIVE_INFINITY so negative-space selections aggregate correctly - getCanvasPosition(): reject results outside MAX_ABS_COORD so bad coordinates cannot be written back to the server - fit(): guard zero-dimension container; use clampScale helper birdseye-view.service.ts (refresh()): bail early before divide-by-scale arithmetic when this.x / this.y / this.k are out of bounds. transform.effects.ts (restoreViewport$): upgrade isFinite checks to isFiniteInBound + isScaleInBound; call storage.removeItem(name) when the persisted entry is invalid, breaking the page-refresh replay loop. ## Reusable canvas / birdseye guards ui/common/canvas/canvas.component.ts: - readViewportTransform: upgrade isFinite to magnitude-bounded checks - Zoom handler: all-or-nothing guard before SVG attribute write and emit - applyTransform: magnitude+scale guard at top of method - restoreViewportFromStorage: evict bad localStorage entries on load - fitContent: clampScale instead of raw Math.min/max - getCanvasPosition: reject out-of-bounds output ui/common/birdseye/birdseye.component.ts: add isTransformSafe predicate; consult it in updateBrush and renderComponents before any divide-by-scale render path. ## Tests - canvas-bounds.util.spec.ts: 30 new tests - canvas-view.service.spec.ts: 12 new tests (mountCanvasDom fixture) - transform.effects.spec.ts: 5 new tests (new file) - birdseye-view.service.spec.ts: 3 new tests - birdseye.component.spec.ts: 4 new tests; update scale-0.1 test to use valid MAX_SCALE to keep the minimum-brush-size assertion accurate - canvas.component.spec.ts: 10 new tests - flow.effects.spec.ts: 4 new tests - connector-canvas.effects.spec.ts: 3 new tests; update bare label fixture to carry a position so it survives sanitization unchanged All 2700 tests pass. nx lint nifi and npm run prettier clean.
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.
Summary
NIFI-16025
The NiFi backend can persist arbitrarily large finite floating-point values as component position coordinates (e.g.
7.49e+307). Values of this magnitude passNumber.isFinite()and therefore clear all existing validity checks, but they cause two observable failures in the browser:Error: <g> attribute transform: Expected number— because SVG and float32 rendering pipelines cannot represent numbers of this magnitude.This change adds a layered, UI-only defence across the Angular frontend canvas pipeline. No backend changes are required.
Shared bounds utilities (
libs/shared/src/services/canvas-bounds.util.ts)Introduces a new
canvas-bounds.util.tsmodule with the following exports:MAX_ABS_COORD(1e9) — maximum safe absolute value for a canvas position coordinate.MAX_ABS_TRANSLATE(1e12) — maximum safe absolute value for a viewport translation.MIN_SCALE/MAX_SCALE— canonical scale range constants, centralising values previously hardcoded incanvas-view.service.ts.isFiniteInBound(value, bound)— type guard that combinesNumber.isFinitewith a magnitude check.isScaleInBound(value)— rejects near-zero and out-of-range scale factors.clampScale(scale, fallback?)— NaN-safe scale clamp.sanitizePosition(position, opts)— clamps out-of-range coordinates to(0, 0)and emits a deduplicatedconsole.warnper component ID.Data-ingestion sanitization (flow.effects.ts, connector-canvas.effects.ts)
loadProcessGroup$andloadConnectorFlow$now walk all positioned entity collections — processors, process groups, remote process groups, input/output ports, labels, funnels, and connection bend points — throughsanitizePositionbefore dispatching NgRx actions. A session-scopedSetprevents repeated console warnings during polling refreshes.Transform pipeline guards (canvas-view.service.ts, birdseye-view.service.ts, transform.effects.ts)
canvas-view.service.ts: replaces!isNaNchecks withisFiniteInBound+isScaleInBoundin the zoom handler,transform(),getCanvasPosition(), andfit(); fixesgetSelectionBoundingClientRect()to returnnullon empty selection and to useInfinitysentinels instead ofNumber.MAX_VALUE/Number.MIN_VALUE.birdseye-view.service.ts: bails early inrefresh()before any divide-by-scale arithmetic when the current transform is out of bounds.transform.effects.ts: upgradesisFinitechecks inrestoreViewport$to magnitude-bounded guards; evicts invalid localStorage entries so a corrupted persisted viewport cannot replay across page refreshes.Reusable canvas and birdseye guards (canvas.component.ts, birdseye.component.ts)
canvas.component.ts: applies magnitude-and-scale guards inreadViewportTransform, the zoom handler,applyTransform,restoreViewportFromStorage,fitContent, andgetCanvasPosition; invalid localStorage entries are evicted on load.birdseye.component.ts: introduces anisTransformSafepredicate consulted by bothupdateBrushandrenderComponentsbefore any divide-by-scale render path.Tests
71 new tests added across 8 files:
canvas-bounds.util.spec.tscanvas-view.service.spec.tscanvas.component.spec.tstransform.effects.spec.tsbirdseye.component.spec.tsflow.effects.spec.tsbirdseye-view.service.spec.tsconnector-canvas.effects.spec.tsTest coverage includes the
~7.49e+307fingerprint value, warn-once deduplication across poll cycles, localStorage eviction on load, and the full guard surface in each service and component. All 2,700 existing frontend tests continue to pass.Tracking
Please complete the following tracking steps prior to pull request creation.
Issue Tracking
Pull Request Tracking
NIFI-00000NIFI-00000VerifiedstatusPull Request Formatting
mainbranchVerification
Please indicate the verification steps performed prior to pull request creation.
Build
./mvnw clean install -P contrib-checkLicensing
LICENSEandNOTICEfilesDocumentation