diff --git a/goldens/material/bottom-sheet/index.api.md b/goldens/material/bottom-sheet/index.api.md index 82c18018fbeb..1e99cde0cc4f 100644 --- a/goldens/material/bottom-sheet/index.api.md +++ b/goldens/material/bottom-sheet/index.api.md @@ -83,7 +83,7 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes enter(): void; exit(): void; // (undocumented) - protected _handleAnimationEvent(isStart: boolean, animationName: string): void; + protected _handleAnimationEvent(isStart: boolean, animationName: string, target: EventTarget | null): void; // (undocumented) ngOnDestroy(): void; // (undocumented) diff --git a/scripts/approve-api-golden.mts b/scripts/approve-api-golden.mts index fd3a417c6fed..84a6964ca5a4 100644 --- a/scripts/approve-api-golden.mts +++ b/scripts/approve-api-golden.mts @@ -1,11 +1,11 @@ #!/usr/bin/env node import chalk from 'chalk'; +import {execFileSync} from 'child_process'; import {join} from 'path'; import sh from 'shelljs'; import {guessPackageName} from './util.mjs'; -const bazel = process.env['BAZEL'] || 'pnpm -s bazel'; const targetsToRun = new Set(); if (process.argv.length < 3) { @@ -34,5 +34,5 @@ for (const searchPackageName of process.argv.slice(2)) { } for (const target of targetsToRun) { - sh.exec(`${bazel} run ${target}`); + execFileSync('pnpm', ['-s', 'bazel', 'run', target], {stdio: 'inherit'}); } diff --git a/scripts/create-package-archives.mjs b/scripts/create-package-archives.mjs index 537e0e782a05..8c7e93861d20 100755 --- a/scripts/create-package-archives.mjs +++ b/scripts/create-package-archives.mjs @@ -10,6 +10,7 @@ */ import {join} from 'path'; +import {execFileSync} from 'child_process'; import sh from 'shelljs'; import chalk from 'chalk'; import yargs from 'yargs'; @@ -40,16 +41,38 @@ const builtPackages = sh // If multiple packages should be archived, we also generate a single archive that // contains all packages. This makes it easier to transfer the release packages. if (builtPackages.length > 1) { - console.info('Creating archive with all packages..'); - sh.exec( - `tar --create --gzip --directory ${releasesDir} --file ${archivesDir}/all-${suffix}.tgz .`, + console.info('Creating archive with all packages...'); + execFileSync( + 'tar', + [ + '--create', + '--gzip', + '--directory', + releasesDir, + '--file', + `${archivesDir}/all-${suffix}.tgz`, + '.', + ], + {stdio: 'inherit'}, ); } +// Note that we're using `exec` here, rather than interpolating the arguments into `sh.exec`, +// to avoid a potential command injection through the `suffix` which is user-provided. for (const pkg of builtPackages) { console.info(`Creating archive for package: ${pkg.name}`); - sh.exec( - `tar --create --gzip --directory ${pkg.path} --file ${archivesDir}/${pkg.name}-${suffix}.tgz .`, + execFileSync( + 'tar', + [ + '--create', + '--gzip', + '--directory', + pkg.path, + '--file', + `${archivesDir}/${pkg.name}-${suffix}.tgz`, + '.', + ], + {stdio: 'inherit'}, ); } diff --git a/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts b/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts index 3685fc2e65d7..0b41a08c8f8d 100644 --- a/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts +++ b/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts @@ -60,7 +60,7 @@ export class KeyboardEventManager extends EventManager< } private _normalizeInputs(...args: unknown[]) { - const withModifiers = Array.isArray(args[0]) || (args[0] as string) in Modifier; + const withModifiers = Array.isArray(args[0]) || Modifier.hasOwnProperty(args[0] as string); const modifiers = withModifiers ? args[0] : Modifier.None; const key = withModifiers ? args[1] : args[0]; const handler = withModifiers ? args[2] : args[1]; diff --git a/src/cdk/layout/media-matcher.ts b/src/cdk/layout/media-matcher.ts index 8459b90021be..cdce864b36e3 100644 --- a/src/cdk/layout/media-matcher.ts +++ b/src/cdk/layout/media-matcher.ts @@ -73,7 +73,12 @@ function createEmptyStyleRule(query: string, nonce: string | undefined | null) { } if (mediaQueryStyleNode.sheet) { - mediaQueryStyleNode.sheet.insertRule(`@media ${query} {body{ }}`, 0); + mediaQueryStyleNode.sheet.insertRule( + // Drop the curly braces to avoid injection attacks. Curly braces aren't + // valid media query syntax so this should be a no-op in valid cases. + `@media ${query.replace(/[{}]/g, '')} {body{ }}`, + 0, + ); mediaQueriesForWebkitCompatibility.add(query); } } catch (e) { diff --git a/src/material/bottom-sheet/bottom-sheet-container.ts b/src/material/bottom-sheet/bottom-sheet-container.ts index 49bbc54d830f..93331d8df6e1 100644 --- a/src/material/bottom-sheet/bottom-sheet-container.ts +++ b/src/material/bottom-sheet/bottom-sheet-container.ts @@ -46,9 +46,9 @@ const EXIT_ANIMATION = '_mat-bottom-sheet-exit'; '[attr.role]': '_config.role', '[attr.aria-modal]': '_config.ariaModal', '[attr.aria-label]': '_config.ariaLabel', - '(animationstart)': '_handleAnimationEvent(true, $event.animationName)', - '(animationend)': '_handleAnimationEvent(false, $event.animationName)', - '(animationcancel)': '_handleAnimationEvent(false, $event.animationName)', + '(animationstart)': '_handleAnimationEvent(true, $event.animationName, $event.target)', + '(animationend)': '_handleAnimationEvent(false, $event.animationName, $event.target)', + '(animationcancel)': '_handleAnimationEvent(false, $event.animationName, $event.target)', }, imports: [CdkPortalOutlet], }) @@ -125,8 +125,8 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes private _simulateAnimation(name: typeof ENTER_ANIMATION | typeof EXIT_ANIMATION) { this._ngZone.run(() => { - this._handleAnimationEvent(true, name); - setTimeout(() => this._handleAnimationEvent(false, name)); + this._handleAnimationEvent(true, name, this._elementRef.nativeElement); + setTimeout(() => this._handleAnimationEvent(false, name, this._elementRef.nativeElement)); }); } @@ -139,15 +139,21 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes super._trapFocus({preventScroll: true}); } - protected _handleAnimationEvent(isStart: boolean, animationName: string) { - const isEnter = animationName === ENTER_ANIMATION; - const isExit = animationName === EXIT_ANIMATION; - - if (isEnter || isExit) { - this._animationStateChanged.emit({ - toState: isEnter ? 'visible' : 'hidden', - phase: isStart ? 'start' : 'done', - }); + protected _handleAnimationEvent( + isStart: boolean, + animationName: string, + target: EventTarget | null, + ) { + if (target === this._elementRef.nativeElement) { + const isEnter = animationName === ENTER_ANIMATION; + const isExit = animationName === EXIT_ANIMATION; + + if (isEnter || isExit) { + this._animationStateChanged.emit({ + toState: isEnter ? 'visible' : 'hidden', + phase: isStart ? 'start' : 'done', + }); + } } } } diff --git a/src/material/grid-list/tile-coordinator.ts b/src/material/grid-list/tile-coordinator.ts index 14ebc161a39a..cbd84c0a73db 100644 --- a/src/material/grid-list/tile-coordinator.ts +++ b/src/material/grid-list/tile-coordinator.ts @@ -94,10 +94,9 @@ export class TileCoordinator { /** Finds the next available space large enough to fit the tile. */ private _findMatchingGap(tileCols: number): number { - if (tileCols > this.tracker.length && (typeof ngDevMode === 'undefined' || ngDevMode)) { + if (tileCols > this.tracker.length) { throw Error( - `mat-grid-list: tile with colspan ${tileCols} is wider than ` + - `grid with cols="${this.tracker.length}".`, + `mat-grid-list: tile with colspan ${tileCols} is wider than grid with cols="${this.tracker.length}".`, ); } diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index edf98e72b2db..394966d9968e 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -203,7 +203,13 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten return this._animationDuration; } set animationDuration(value: string) { - this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value; + if (/^[0-9]+(?:\.[0-9]+)?$/.test(value)) { + this._animationDuration = value + 'ms'; + } else if (/^[0-9]+(?:\.[0-9]+)?(?:ms|s)$/.test(value)) { + this._animationDuration = value; + } else { + this._animationDuration = ''; + } } private _animationDuration = ''; diff --git a/tools/markdown-to-html/docs-marked-renderer.mts b/tools/markdown-to-html/docs-marked-renderer.mts index 1432b9860f51..8589c6afada7 100644 --- a/tools/markdown-to-html/docs-marked-renderer.mts +++ b/tools/markdown-to-html/docs-marked-renderer.mts @@ -102,11 +102,11 @@ export class DocsMarkdownRenderer extends Renderer { file: string; region: string; }; - replacement = `
`; + replacement = `
`; } else { - replacement = `
`; + replacement = `
`; } return `${exampleStartMarker}${replacement}${exampleEndMarker}`; @@ -154,4 +154,17 @@ export class DocsMarkdownRenderer extends Renderer { return `${markdownOpen}${output}`; } + + private _escapeHtml(text: string): string { + if (!text) { + return text; + } + + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } }