diff --git a/.mapping.json b/.mapping.json index b7853f89c..75fa4ec02 100644 --- a/.mapping.json +++ b/.mapping.json @@ -12076,6 +12076,7 @@ "client/web/divkit/src/utils/nonNegativeModulo.ts":"divkit/public/client/web/divkit/src/utils/nonNegativeModulo.ts", "client/web/divkit/src/utils/padLeft.ts":"divkit/public/client/web/divkit/src/utils/padLeft.ts", "client/web/divkit/src/utils/prepareBase64.ts":"divkit/public/client/web/divkit/src/utils/prepareBase64.ts", + "client/web/divkit/src/utils/previewValue.ts":"divkit/public/client/web/divkit/src/utils/previewValue.ts", "client/web/divkit/src/utils/propToString.ts":"divkit/public/client/web/divkit/src/utils/propToString.ts", "client/web/divkit/src/utils/pxToEm.ts":"divkit/public/client/web/divkit/src/utils/pxToEm.ts", "client/web/divkit/src/utils/shadow.ts":"divkit/public/client/web/divkit/src/utils/shadow.ts", @@ -14220,6 +14221,7 @@ "client/web/divkit/tests/templates/string_enum_property.test.ts":"divkit/public/client/web/divkit/tests/templates/string_enum_property.test.ts", "client/web/divkit/tests/utils/__snapshots__/applyTemplate.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/applyTemplate.test.ts.snap", "client/web/divkit/tests/utils/__snapshots__/background.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/background.test.ts.snap", + "client/web/divkit/tests/utils/__snapshots__/correctBooleanInt.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/correctBooleanInt.test.ts.snap", "client/web/divkit/tests/utils/__snapshots__/filters.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/filters.test.ts.snap", "client/web/divkit/tests/utils/__snapshots__/simpleCheckInput.test.ts.snap":"divkit/public/client/web/divkit/tests/utils/__snapshots__/simpleCheckInput.test.ts.snap", "client/web/divkit/tests/utils/applyTemplate.test.ts":"divkit/public/client/web/divkit/tests/utils/applyTemplate.test.ts", diff --git a/client/web/divkit/src/components/Root.svelte b/client/web/divkit/src/components/Root.svelte index 74ba0457a..ca58d3d8a 100644 --- a/client/web/divkit/src/components/Root.svelte +++ b/client/web/divkit/src/components/Root.svelte @@ -77,6 +77,7 @@ import { arrayInsert, arrayRemove } from '../actions/array'; import { copyToClipboard } from '../actions/copyToClipboard'; import { filterEnabledActions } from '../utils/filterEnabledActions'; + import { correctBooleanInt } from '../utils/correctBooleanInt'; export let id: string; export let json: Partial = {}; @@ -718,7 +719,7 @@ const actionUrl = action.url ? String(action.url) : ''; const actionTyped = action.typed; - if (!filterEnabledActions(action)) { + if (!filterEnabledActions(action, logError)) { return; } @@ -902,7 +903,7 @@ return; } - const filtered = actions.filter(filterEnabledActions); + const filtered = actions.filter(action => filterEnabledActions(action, logError)); for (let i = 0; i < filtered.length; ++i) { let action = filtered[i]; @@ -1529,7 +1530,7 @@ if ( // if condition is truthy - conditionResult.value && + correctBooleanInt(conditionResult.value, false, logError) && // and trigger mode matches (mode === 'on_variable' || mode === 'on_condition' && prevConditionResult === false) ) { diff --git a/client/web/divkit/src/components/container/Container.svelte b/client/web/divkit/src/components/container/Container.svelte index 1b15082a3..c081be4de 100644 --- a/client/web/divkit/src/components/container/Container.svelte +++ b/client/web/divkit/src/components/container/Container.svelte @@ -54,6 +54,7 @@ import { ContentAlignmentHorizontalMapped, correctContentAlignmentHorizontal } from '../../utils/correctContentAlignmentHorizontal'; import { Truthy } from '../../utils/truthy'; import { assignIfDifferent } from '../../utils/assignIfDifferent'; + import { correctBooleanInt } from '../../utils/correctBooleanInt'; export let componentContext: ComponentContext; export let layoutParams: LayoutParams | undefined = undefined; @@ -137,7 +138,7 @@ } const selectorVal = componentContext.getJsonWithVars(prototype.selector, additionalVars); - if (selectorVal) { + if (correctBooleanInt(selectorVal, true, componentContext.logError)) { div = prototype.div; break; } @@ -209,9 +210,12 @@ if (style) { separator = { - show_at_start: Boolean($jsonSeparator.show_at_start ?? false), - show_at_end: Boolean($jsonSeparator.show_at_end ?? false), - show_between: Boolean($jsonSeparator.show_between ?? true), + show_at_start: + correctBooleanInt($jsonSeparator.show_at_start, false, componentContext.logError), + show_at_end: + correctBooleanInt($jsonSeparator.show_at_end, false, componentContext.logError), + show_between: + correctBooleanInt($jsonSeparator.show_between, true, componentContext.logError), style, margins: prepareMargins($jsonSeparator.margins) }; @@ -233,9 +237,12 @@ if (style) { lineSeparator = { - show_at_start: Boolean($jsonLineSeparator.show_at_start ?? false), - show_at_end: Boolean($jsonLineSeparator.show_at_end ?? false), - show_between: Boolean($jsonLineSeparator.show_between ?? true), + show_at_start: + correctBooleanInt($jsonLineSeparator.show_at_start, false, componentContext.logError), + show_at_end: + correctBooleanInt($jsonLineSeparator.show_at_end, false, componentContext.logError), + show_between: + correctBooleanInt($jsonLineSeparator.show_between, true, componentContext.logError), style, margins: prepareMargins($jsonLineSeparator.margins) }; @@ -308,7 +315,7 @@ valign: contentVAlign, halign: contentHAlign, wrap, - overflow: ($jsonClipToBounds === false || $jsonClipToBounds === 0) ? 'visible' : undefined + overflow: correctBooleanInt($jsonClipToBounds, true, componentContext.logError) ? undefined : 'visible' }; $: style = { diff --git a/client/web/divkit/src/components/gallery/Gallery.svelte b/client/web/divkit/src/components/gallery/Gallery.svelte index ca4a6b00d..75d706c61 100644 --- a/client/web/divkit/src/components/gallery/Gallery.svelte +++ b/client/web/divkit/src/components/gallery/Gallery.svelte @@ -31,6 +31,7 @@ import { debounce } from '../../utils/debounce'; import { Truthy } from '../../utils/truthy'; import { nonNegativeModulo } from '../../utils/nonNegativeModulo'; + import { correctBooleanInt } from '../../utils/correctBooleanInt'; export let componentContext: ComponentContext; export let layoutParams: LayoutParams | undefined = undefined; @@ -228,6 +229,8 @@ [gridTemplate]: joinTemplateSizes(templateSizes) }; + $: restrictScroll = correctBooleanInt($jsonRestrictParentScroll, false, componentContext.logError); + $: mods = { orientation, 'scroll-snap': scrollSnap, @@ -502,7 +505,7 @@ {replaceItems} >
; export let layoutParams: LayoutParams | undefined = undefined; @@ -145,6 +146,8 @@ $: alt = $jsonA11y?.description || ''; + $: preload = correctBooleanInt($jsonPreloadRequired, false, componentContext.logError); + $: { const newRatio = $jsonAspect?.ratio; if (newRatio && isPositiveNumber(newRatio)) { @@ -249,7 +252,7 @@ bind:this={img} class={css.image__image} src={state === STATE_ERROR ? FALLBACK_IMAGE : imageUrl} - loading={$jsonPreloadRequired ? 'eager' : 'lazy'} + loading={preload ? 'eager' : 'lazy'} decoding="async" style={makeStyle(style)} {alt} @@ -263,7 +266,7 @@ bind:this={img} class={css.image__image} src={state === STATE_ERROR ? FALLBACK_IMAGE : imageUrl} - loading={$jsonPreloadRequired ? 'eager' : 'lazy'} + loading={preload ? 'eager' : 'lazy'} decoding="async" style={makeStyle(style)} {alt} diff --git a/client/web/divkit/src/components/input/Input.svelte b/client/web/divkit/src/components/input/Input.svelte index ab7117775..86198df6f 100644 --- a/client/web/divkit/src/components/input/Input.svelte +++ b/client/web/divkit/src/components/input/Input.svelte @@ -48,6 +48,7 @@ import { correctAlignmentHorizontal } from '../../utils/correctAlignmentHorizontal'; import { AlignmentVerticalMapped, correctAlignmentVertical } from '../../utils/correctAlignmentVertical'; import { calcSelectionOffset, setSelectionOffset } from '../../utils/contenteditable'; + import { correctBooleanInt } from '../../utils/correctBooleanInt'; export let componentContext: ComponentContext; export let layoutParams: LayoutParams | undefined = undefined; @@ -213,6 +214,8 @@ $: isMultiline = keyboardType === 'multi_line_text'/* && isPositiveNumber($jsonVisibleMaxLines) && $jsonVisibleMaxLines > 1*/; + $: selectAllOnFocus = correctBooleanInt($jsonSelectAll, false, componentContext.logError); + $: { if (isPositiveNumber($jsonVisibleMaxLines)) { maxHeight = `calc(${$jsonVisibleMaxLines * (lineHeight || 1.25) * (fontSize / 10) + 'em'} + ${pxToEmWithUnits(correctNonNegativeNumber($jsonPaddings?.top, 0) + correctNonNegativeNumber($jsonPaddings?.bottom, 0))})`; @@ -450,8 +453,8 @@ bind:innerText={contentEditableValue} on:input={onInput} on:paste={onPaste} - on:mousedown={$jsonSelectAll ? onMousedown : undefined} - on:click={$jsonSelectAll ? onClick : undefined} + on:mousedown={selectAllOnFocus ? onMousedown : undefined} + on:click={selectAllOnFocus ? onClick : undefined} on:focus={focusHandler} on:blur={blurHandler} > @@ -470,8 +473,8 @@ {placeholder} {value} on:input={onInput} - on:mousedown={$jsonSelectAll ? onMousedown : undefined} - on:click={$jsonSelectAll ? onClick : undefined} + on:mousedown={selectAllOnFocus ? onMousedown : undefined} + on:click={selectAllOnFocus ? onClick : undefined} on:focus={focusHandler} on:blur={blurHandler} > diff --git a/client/web/divkit/src/components/pager/Pager.svelte b/client/web/divkit/src/components/pager/Pager.svelte index 01c9528e0..f58a8df06 100644 --- a/client/web/divkit/src/components/pager/Pager.svelte +++ b/client/web/divkit/src/components/pager/Pager.svelte @@ -27,6 +27,7 @@ import { debounce } from '../../utils/debounce'; import { Truthy } from '../../utils/truthy'; import { nonNegativeModulo } from '../../utils/nonNegativeModulo'; + import { correctBooleanInt } from '../../utils/correctBooleanInt'; export let componentContext: ComponentContext; export let layoutParams: LayoutParams | undefined = undefined; @@ -140,6 +141,8 @@ } } + $: restrictScroll = correctBooleanInt($jsonRestrictParentScroll, false, componentContext.logError); + $: style = { 'grid-gap': itemSpacing, padding, @@ -318,7 +321,7 @@ {replaceItems} >
; export let layoutParams: LayoutParams | undefined = undefined; @@ -324,7 +325,7 @@ } $: { - if ($jsonSeparator) { + if (correctBooleanInt($jsonSeparator, false, componentContext.logError)) { if ($jsonSeparatorColor) { separatorBackground = correctColor($jsonSeparatorColor, 1, separatorBackground); } @@ -338,14 +339,14 @@ margin: separatorMargins }; - $: isSwipeEnabled = typeof $jsonSwipeEnabled === 'undefined' ? - true : - Boolean($jsonSwipeEnabled); + $: isSwipeEnabled = correctBooleanInt($jsonSwipeEnabled, true, componentContext.logError); $: { titlePadding = correctEdgeInsertsObject($jsonTitlePaddings ? $jsonTitlePaddings : undefined, titlePadding); } + $: restrictScroll = correctBooleanInt($jsonRestrictParentScroll, false, componentContext.logError); + function updateItems(items: MaybeMissing[]): void { if (hasError) { return; @@ -661,7 +662,7 @@
filterEnabledActions(action, componentContext.logError)) : [] } attrs={{ @@ -714,7 +716,7 @@ >
{/if}
filterEnabledActions(action, componentContext.logError)) || undefined }); } @@ -362,7 +362,7 @@ height: imageHeight, wrapperStyle, svgFilterId, - preloadRequired: Boolean(item.image.preload_required) + preloadRequired: correctBooleanInt(item.image.preload_required, false, componentContext.logError) } }); } diff --git a/client/web/divkit/src/components/utilities/Outer.svelte b/client/web/divkit/src/components/utilities/Outer.svelte index 5f2e0393f..b6bbb2eb6 100644 --- a/client/web/divkit/src/components/utilities/Outer.svelte +++ b/client/web/divkit/src/components/utilities/Outer.svelte @@ -71,6 +71,7 @@ import { isDeepEqual } from '../../utils/isDeepEqual'; import { filterEnabledActions } from '../../utils/filterEnabledActions'; import { isPrefersReducedMotion } from '../../utils/isPrefersReducedMotion'; + import { correctBooleanInt } from '../../utils/correctBooleanInt'; import Actionable from './Actionable.svelte'; import OuterBackground from './OuterBackground.svelte'; @@ -262,7 +263,7 @@ let newBackgroundRadius = ''; if (border) { - if (border.has_shadow) { + if (correctBooleanInt(border.has_shadow, false, componentContext.logError)) { const shadow = border.shadow; if (shadow) { newBorderStyle['box-shadow'] = shadowToCssBoxShadow(shadow); @@ -348,7 +349,8 @@ ) { widthType = 'content'; if ( - type === 'wrap_content' && $jsonWidth?.constrained || + type === 'wrap_content' && + correctBooleanInt($jsonWidth?.constrained, false, componentContext.logError) || (type === 'match_parent' || !type) && layoutParams.parentHorizontalWrapContent ) { newWidthMods['width-constrained'] = true; @@ -457,7 +459,8 @@ } else { heightType = 'content'; if ( - type === 'wrap_content' && $jsonHeight?.constrained || + type === 'wrap_content' && + correctBooleanInt($jsonHeight?.constrained, false, componentContext.logError) || type === 'match_parent' && layoutParams.parentVerticalWrapContent ) { newHeightMods['height-constrained'] = true; @@ -615,12 +618,14 @@ componentContext.logError(wrapError(new Error(`Cannot use action on component "${customActions}"`))); } + const filterFn = (action: MaybeMissing) => filterEnabledActions(action, componentContext.logError); + // todo check parent actions with customActions - actions = newActions.filter(filterEnabledActions); - doubleTapActions = newDoubleTapActions.filter(filterEnabledActions); - longTapActions = newLongTapActions.filter(filterEnabledActions); - focusActions = newFocusActions.filter(filterEnabledActions); - blurActions = newBlurActions.filter(filterEnabledActions); + actions = newActions.filter(filterFn); + doubleTapActions = newDoubleTapActions.filter(filterFn); + longTapActions = newLongTapActions.filter(filterFn); + focusActions = newFocusActions.filter(filterFn); + blurActions = newBlurActions.filter(filterFn); } $: { diff --git a/client/web/divkit/src/components/video/Video.svelte b/client/web/divkit/src/components/video/Video.svelte index cabd2a8d4..24df99809 100644 --- a/client/web/divkit/src/components/video/Video.svelte +++ b/client/web/divkit/src/components/video/Video.svelte @@ -84,11 +84,11 @@ } } - $: loop = correctBooleanInt($jsonRepeatable, loop); + $: loop = correctBooleanInt($jsonRepeatable, loop, componentContext.logError); - $: autoplay = correctBooleanInt($jsonAutostart, autoplay); + $: autoplay = correctBooleanInt($jsonAutostart, autoplay, componentContext.logError); - $: muted = correctBooleanInt($jsonMuted, muted); + $: muted = correctBooleanInt($jsonMuted, muted, componentContext.logError); $: poster = typeof $jsonPreview === 'string' ? prepareBase64($jsonPreview) : poster; diff --git a/client/web/divkit/src/use/visibilityAction.ts b/client/web/divkit/src/use/visibilityAction.ts index 4b924bfad..e02e7aed8 100644 --- a/client/web/divkit/src/use/visibilityAction.ts +++ b/client/web/divkit/src/use/visibilityAction.ts @@ -5,6 +5,7 @@ import type { MaybeMissing } from '../expressions/json'; import type { ComponentContext } from '../types/componentContext'; import { getUrlSchema, isBuiltinSchema } from '../utils/url'; import { correctNonNegativeNumber } from '../utils/correctNonNegativeNumber'; +import { correctBooleanInt } from '../utils/correctBooleanInt'; interface CalcedAction { index: number | undefined; @@ -30,10 +31,6 @@ function checkPercentage(isVisibility: boolean, val: number | undefined, default return defaultVal; } -function filterActions(it: CalcedAction): it is IndexedCalcedAction { - return it.is_enabled !== 0 && it.is_enabled !== false && it.index !== undefined; -} - export function visibilityAction(node: HTMLElement, { visibilityActions, disappearActions, @@ -43,7 +40,7 @@ export function visibilityAction(node: HTMLElement, { visibilityActions?: MaybeMissing[]; disappearActions?: MaybeMissing[]; rootCtx: RootCtxValue; - componentContext: ComponentContext; + componentContext: ComponentContext }) { const visibilityStatus: { type: 'visibility' | 'disappear'; @@ -110,8 +107,12 @@ export function visibilityAction(node: HTMLElement, { const totalStore = derived(calcedList, values => values); + const filterActions = (it: CalcedAction): it is IndexedCalcedAction => { + return correctBooleanInt(it.is_enabled, true, componentContext.logError); + }; + const unsubscribe = totalStore.subscribe(values => { - const filtered = values.filter(filterActions); + const filtered = values.filter(filterActions); const map: Record = {}; filtered.forEach(it => { diff --git a/client/web/divkit/src/utils/correctBooleanInt.ts b/client/web/divkit/src/utils/correctBooleanInt.ts index 228e7257a..4e6d647cb 100644 --- a/client/web/divkit/src/utils/correctBooleanInt.ts +++ b/client/web/divkit/src/utils/correctBooleanInt.ts @@ -1,6 +1,16 @@ -export function correctBooleanInt(val: number | boolean | undefined, defaultVal: boolean): boolean { +import { previewValue } from './previewValue'; +import { LogError, wrapError } from './wrapError'; + +export function correctBooleanInt( + val: unknown, + defaultVal: boolean, + logError: LogError +): boolean { if (val === 1 || val === 0 || val === false || val === true) { return Boolean(val); } + if (val !== undefined) { + logError(wrapError(new Error(`Invalid value: ${previewValue(val)}. Expression expected.`))); + } return defaultVal; } diff --git a/client/web/divkit/src/utils/filterEnabledActions.ts b/client/web/divkit/src/utils/filterEnabledActions.ts index 58fb43d5b..f69401ba2 100644 --- a/client/web/divkit/src/utils/filterEnabledActions.ts +++ b/client/web/divkit/src/utils/filterEnabledActions.ts @@ -1,6 +1,11 @@ import type { Action, DisappearAction, VisibilityAction } from '../../typings/common'; import type { MaybeMissing } from '../expressions/json'; +import { correctBooleanInt } from './correctBooleanInt'; +import { LogError } from './wrapError'; -export function filterEnabledActions(action: MaybeMissing): boolean { - return action.is_enabled !== 0 && action.is_enabled !== false; +export function filterEnabledActions( + action: MaybeMissing, + logError: LogError +): boolean { + return correctBooleanInt(action.is_enabled, true, logError); } diff --git a/client/web/divkit/src/utils/previewValue.ts b/client/web/divkit/src/utils/previewValue.ts new file mode 100644 index 000000000..2bb0c92aa --- /dev/null +++ b/client/web/divkit/src/utils/previewValue.ts @@ -0,0 +1,13 @@ +export function previewValue(value: unknown): string { + if (typeof value === 'object' && value) { + if (Array.isArray(value)) { + return 'array'; + } + return 'object'; + } else if (value === null) { + return 'null'; + } else if (value === undefined) { + return 'undefined'; + } + return JSON.stringify(value); +} diff --git a/client/web/divkit/src/utils/updateFixedMask.ts b/client/web/divkit/src/utils/updateFixedMask.ts index 7477519cd..a82ce480b 100644 --- a/client/web/divkit/src/utils/updateFixedMask.ts +++ b/client/web/divkit/src/utils/updateFixedMask.ts @@ -3,6 +3,7 @@ import type { MaskData } from './mask/baseInputMask'; import { FixedLengthInputMask } from './mask/fixedLengthInputMask'; import { MaybeMissing } from '../expressions/json'; import { FixedLengthInputMask as FixedLengthInputMaskType } from '../types/input'; +import { correctBooleanInt } from './correctBooleanInt'; export function updateFixedMask( mask: MaybeMissing, @@ -15,7 +16,7 @@ export function updateFixedMask( ) { const maskData: MaskData = { pattern: mask.pattern, - alwaysVisible: Boolean(mask.always_visible), + alwaysVisible: correctBooleanInt(mask.always_visible, false, logError), decoding: mask.pattern_elements.map(it => ({ key: it.key as string, filter: it.regex && typeof it.regex === 'string' ? it.regex : undefined, diff --git a/client/web/divkit/tests/utils/__snapshots__/correctBooleanInt.test.ts.snap b/client/web/divkit/tests/utils/__snapshots__/correctBooleanInt.test.ts.snap new file mode 100644 index 000000000..8fc54f824 --- /dev/null +++ b/client/web/divkit/tests/utils/__snapshots__/correctBooleanInt.test.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`correctBooleanInt simple 1`] = ` +[ + [ + [Error: Invalid value: 2. Expression expected.], + ], + [ + [Error: Invalid value: 2. Expression expected.], + ], +] +`; diff --git a/client/web/divkit/tests/utils/correctBooleanInt.test.ts b/client/web/divkit/tests/utils/correctBooleanInt.test.ts index 2b2553c8c..c7a19ce5a 100644 --- a/client/web/divkit/tests/utils/correctBooleanInt.test.ts +++ b/client/web/divkit/tests/utils/correctBooleanInt.test.ts @@ -2,17 +2,21 @@ import { correctBooleanInt } from '../../src/utils/correctBooleanInt'; describe('correctBooleanInt', () => { test('simple', () => { - expect(correctBooleanInt(2, false)).toBe(false); - expect(correctBooleanInt(2, true)).toBe(true); - expect(correctBooleanInt(1, false)).toBe(true); - expect(correctBooleanInt(1, true)).toBe(true); - expect(correctBooleanInt(0, false)).toBe(false); - expect(correctBooleanInt(0, true)).toBe(false); - expect(correctBooleanInt(true, false)).toBe(true); - expect(correctBooleanInt(true, true)).toBe(true); - expect(correctBooleanInt(false, false)).toBe(false); - expect(correctBooleanInt(false, true)).toBe(false); - expect(correctBooleanInt(undefined, false)).toBe(false); - expect(correctBooleanInt(undefined, true)).toBe(true); + const logError = jest.fn(); + + expect(correctBooleanInt(2, false, logError)).toBe(false); + expect(correctBooleanInt(2, true, logError)).toBe(true); + expect(correctBooleanInt(1, false, logError)).toBe(true); + expect(correctBooleanInt(1, true, logError)).toBe(true); + expect(correctBooleanInt(0, false, logError)).toBe(false); + expect(correctBooleanInt(0, true, logError)).toBe(false); + expect(correctBooleanInt(true, false, logError)).toBe(true); + expect(correctBooleanInt(true, true, logError)).toBe(true); + expect(correctBooleanInt(false, false, logError)).toBe(false); + expect(correctBooleanInt(false, true, logError)).toBe(false); + expect(correctBooleanInt(undefined, false, logError)).toBe(false); + expect(correctBooleanInt(undefined, true, logError)).toBe(true); + + expect(logError.mock.calls).toMatchSnapshot(); }); });