Skip to content

NIFI-16025 Canvas SVG transform overflow guards#11352

Open
rfellows wants to merge 1 commit into
apache:mainfrom
rfellows:NIFI-16025
Open

NIFI-16025 Canvas SVG transform overflow guards#11352
rfellows wants to merge 1 commit into
apache:mainfrom
rfellows:NIFI-16025

Conversation

@rfellows

Copy link
Copy Markdown
Contributor

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 pass Number.isFinite() and therefore clear all existing validity checks, but they cause two observable failures in the browser:

  1. SVG rendering errors — Error: <g> attribute transform: Expected number — because SVG and float32 rendering pipelines cannot represent numbers of this magnitude.
  2. Frozen canvas navigation because D3.js arithmetic overflows when computing zoom and pan transforms from such coordinates.

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.ts module 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 in canvas-view.service.ts.
  • isFiniteInBound(value, bound) — type guard that combines Number.isFinite with 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 deduplicated console.warn per component ID.

Data-ingestion sanitization (flow.effects.ts, connector-canvas.effects.ts)

loadProcessGroup$ and loadConnectorFlow$ now walk all positioned entity collections — processors, process groups, remote process groups, input/output ports, labels, funnels, and connection bend points — through sanitizePosition before dispatching NgRx actions. A session-scoped Set prevents 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 !isNaN checks with isFiniteInBound + isScaleInBound in the zoom handler, transform(), getCanvasPosition(), and fit(); fixes getSelectionBoundingClientRect() to return null on empty selection and to use Infinity sentinels instead of Number.MAX_VALUE / Number.MIN_VALUE.
  • birdseye-view.service.ts: bails early in refresh() before any divide-by-scale arithmetic when the current transform is out of bounds.
  • transform.effects.ts: upgrades isFinite checks in restoreViewport$ 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 in readViewportTransform, the zoom handler, applyTransform, restoreViewportFromStorage, fitContent, and getCanvasPosition; invalid localStorage entries are evicted on load.
  • birdseye.component.ts: introduces an isTransformSafe predicate consulted by both updateBrush and renderComponents before any divide-by-scale render path.

Tests

71 new tests added across 8 files:

File New tests
canvas-bounds.util.spec.ts 30
canvas-view.service.spec.ts 12
canvas.component.spec.ts 10
transform.effects.spec.ts 5
birdseye.component.spec.ts 4
flow.effects.spec.ts 4
birdseye-view.service.spec.ts 3
connector-canvas.effects.spec.ts 3

Test coverage includes the ~7.49e+307 fingerprint 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

  • Pull Request title starts with Apache NiFi Jira issue number, such as NIFI-00000
  • Pull Request commit message starts with Apache NiFi Jira issue number, as such NIFI-00000
  • Pull request contains commits signed with a registered key indicating Verified status

Pull Request Formatting

  • Pull Request based on current revision of the main branch
  • Pull Request refers to a feature branch with one commit containing changes

Verification

Please indicate the verification steps performed prior to pull request creation.

Build

  • Build completed using ./mvnw clean install -P contrib-check
    • JDK 21
    • JDK 25

Licensing

  • New dependencies are compatible with the Apache License 2.0 according to the License Policy
  • New dependencies are documented in applicable LICENSE and NOTICE files

Documentation

  • Documentation formatting appears as expected in rendered files

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.
@rfellows rfellows added the ui Pull requests for work relating to the user interface label Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ui Pull requests for work relating to the user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant