From f2c4fc3ad018099aa843005d6187743d673b5828 Mon Sep 17 00:00:00 2001 From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:46:05 -0500 Subject: [PATCH] fix(material/slider): prevent out of memory errors from tick marks on the server The slider only renders its tick marks once it has a width, but it was computing them regardless, allocating one array entry per tick mark. Since the number of entries is derived from the `min`, `max`, `step` and value, a large range or a small step could result in an array big enough to exhaust the heap. This was especially problematic during server-side rendering, because the slider never has a width on the server so the array was always built and then discarded. If any of those inputs are derived from user input (e.g. query parameters), a single request could crash the server process along with every other request it was handling. These changes skip computing the tick marks until the slider has a width. Fixes #34036. --- src/material/slider/slider.spec.ts | 32 ++++++++++++++++++++++++++++++ src/material/slider/slider.ts | 5 ++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/material/slider/slider.spec.ts b/src/material/slider/slider.spec.ts index 1021ebd6aeeb..d46e64d2dc8c 100644 --- a/src/material/slider/slider.spec.ts +++ b/src/material/slider/slider.spec.ts @@ -1722,6 +1722,38 @@ describe('MatSlider', () => { expect(tickEls.inactive.length).toBe(100); }); }); + + describe('slider with tick marks that does not have a width', () => { + it('should not compute the tick marks until the slider has a width', () => { + const fixture = createComponent(SliderWithTickMarks); + fixture.nativeElement.style.display = 'none'; + fixture.detectChanges(); + const slider = fixture.componentInstance.slider; + expect(slider._tickMarks.length).toBe(0); + + slider.max = 1000; + expect(slider._tickMarks.length).toBe(0); + + fixture.nativeElement.style.display = ''; + slider._onResize(); + expect(slider._tickMarks.length).toBe(1001); + }); + + it('should not compute the tick marks of a range slider until it has a width', () => { + const fixture = createComponent(RangeSliderWithTickMarks); + fixture.nativeElement.style.display = 'none'; + fixture.detectChanges(); + const slider = fixture.componentInstance.slider; + expect(slider._tickMarks.length).toBe(0); + + slider.max = 1000; + expect(slider._tickMarks.length).toBe(0); + + fixture.nativeElement.style.display = ''; + slider._onResize(); + expect(slider._tickMarks.length).toBe(1001); + }); + }); }); const SLIDER_STYLES = ['.mat-mdc-slider { width: 300px; }']; diff --git a/src/material/slider/slider.ts b/src/material/slider/slider.ts index 12f8e52e009f..6de2f36190a8 100644 --- a/src/material/slider/slider.ts +++ b/src/material/slider/slider.ts @@ -366,7 +366,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider { @Input() displayWith: (value: number) => string = (value: number) => `${value}`; /** Used to keep track of & render the active & inactive tick marks on the slider track. */ - _tickMarks!: _MatTickMark[]; + _tickMarks: _MatTickMark[] = []; /** Whether animations have been disabled. */ _noopAnimations = _animationsDisabled(); @@ -874,6 +874,9 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider { _updateTickMarkUI(): void { if ( !this.showTickMarks || + // Tick marks aren't rendered until the slider has a width (e.g. they never are on the + // server) so skip computing them until then since it can be expensive for large ranges. + !this._cachedWidth || this.step === undefined || this.min === undefined || this.max === undefined