From b4a711ea9fe9e89a903575466c0e505be2714afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Thu, 2 Jan 2020 10:35:30 -0800 Subject: [PATCH] refactor(ivy): Delete all styling code. (Part of next SHA) (#34616) NOTE: This change deletes code and creates a BROKEN SHA. If reverting this SHA needs to be reverted with the next SHA to get back into a valid state. PR Close #34616 --- .../core/src/render3/instructions/shared.ts | 28 - .../core/src/render3/instructions/styling.ts | 429 ------- packages/core/src/render3/state.ts | 63 - packages/core/src/render3/styling/bindings.ts | 1108 ----------------- .../src/render3/styling/map_based_bindings.ts | 362 ------ packages/core/src/render3/styling/state.ts | 139 --- .../core/src/render3/styling/styling_debug.ts | 354 ------ .../core/src/render3/util/styling_utils.ts | 463 ------- packages/core/src/util/ng_dev_mode.ts | 22 - .../styling_next/map_based_bindings_spec.ts | 85 -- .../styling_next/styling_context_spec.ts | 136 -- .../styling_next/styling_debug_spec.ts | 85 -- .../test/render3/util/styling_utils_spec.ts | 26 - 13 files changed, 3300 deletions(-) delete mode 100644 packages/core/src/render3/styling/bindings.ts delete mode 100644 packages/core/src/render3/styling/map_based_bindings.ts delete mode 100644 packages/core/src/render3/styling/state.ts delete mode 100644 packages/core/src/render3/util/styling_utils.ts delete mode 100644 packages/core/test/render3/styling_next/map_based_bindings_spec.ts delete mode 100644 packages/core/test/render3/styling_next/styling_context_spec.ts delete mode 100644 packages/core/test/render3/styling_next/styling_debug_spec.ts delete mode 100644 packages/core/test/render3/util/styling_utils_spec.ts diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 733d33a964..f8c87782f9 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -1994,31 +1994,3 @@ export function textBindingInternal(lView: LView, index: number, value: string): isProceduralRenderer(renderer) ? renderer.setValue(element, value) : element.textContent = value; } -/** - * Renders all initial styling (class and style values) on to the element from the tNode. - * - * All initial styling data (i.e. any values extracted from the `style` or `class` attributes - * on an element) are collected into the `tNode.styles` and `tNode.classes` data structures. - * These values are populated during the creation phase of an element and are then later - * applied once the element is instantiated. This function applies each of the static - * style and class entries to the element. - */ -export function renderInitialStyling( - renderer: Renderer3, native: RElement, tNode: TNode, append: boolean) { - if (tNode.classes !== null) { - if (append) { - renderStylingMap(renderer, native, tNode.classes, true); - } else { - const classes = getInitialStylingValue(tNode.classes); - writeStylingValueDirectly(renderer, native, classes, true, null); - } - } - if (tNode.styles !== null) { - if (append) { - renderStylingMap(renderer, native, tNode.styles, false); - } else { - const styles = getInitialStylingValue(tNode.styles); - writeStylingValueDirectly(renderer, native, styles, false, null); - } - } -} diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts index 623eec4382..5a592831fa 100644 --- a/packages/core/src/render3/instructions/styling.ts +++ b/packages/core/src/render3/instructions/styling.ts @@ -25,15 +25,6 @@ import {getNativeByTNode, getTNode} from '../util/view_utils'; -/** - * -------- - * - * This file contains the core logic for how styling instructions are processed in Angular. - * - * To learn more about the algorithm see `TStylingContext`. - * - * -------- - */ /** * Sets the current style sanitizer function which will then be used @@ -83,43 +74,6 @@ export function ɵɵstyleProp( return ɵɵstyleProp; } -/** - * Internal function for applying a single style to an element. - * - * The reason why this function has been separated from `ɵɵstyleProp` is because - * it is also called from `ɵɵstylePropInterpolate`. - */ -export function stylePropInternal( - elementIndex: number, prop: string, value: string | number | SafeValue | null, - suffix?: string | null | undefined): void { - // if a value is interpolated then it may render a `NO_CHANGE` value. - // in this case we do not need to do anything, but the binding index - // still needs to be incremented because all styling binding values - // are stored inside of the lView. - const bindingIndex = nextBindingIndex(); - const lView = getLView(); - const tNode = getTNode(elementIndex, lView); - const firstUpdatePass = lView[TVIEW].firstUpdatePass; - - // we check for this in the instruction code so that the context can be notified - // about prop or map bindings so that the direct apply check can decide earlier - // if it allows for context resolution to be bypassed. - if (firstUpdatePass) { - patchConfig(tNode, TNodeFlags.hasStylePropBindings); - patchHostStylingFlag(tNode, isHostStyling(), false); - } - - const updated = stylingProp( - tNode, firstUpdatePass, lView, bindingIndex, prop, resolveStylePropValue(value, suffix), - false); - if (ngDevMode) { - ngDevMode.styleProp++; - if (updated) { - ngDevMode.stylePropCacheMiss++; - } - } -} - /** * Update a class binding on an element with the provided value. * @@ -164,73 +118,6 @@ export function ɵɵclassProp(className: string, value: boolean | null): typeof return ɵɵclassProp; } -/** - * Shared function used to update a prop-based styling binding for an element. - * - * Depending on the state of the `tNode.styles` styles context, the style/prop - * value may be applied directly to the element instead of being processed - * through the context. The reason why this occurs is for performance and fully - * depends on the state of the context (i.e. whether or not there are duplicate - * bindings or whether or not there are map-based bindings and property bindings - * present together). - */ -function stylingProp( - tNode: TNode, firstUpdatePass: boolean, lView: LView, bindingIndex: number, prop: string, - value: boolean | number | SafeValue | string | null | undefined | NO_CHANGE, - isClassBased: boolean): boolean { - let updated = false; - - const native = getNativeByTNode(tNode, lView) as RElement; - const context = isClassBased ? getClassesContext(tNode) : getStylesContext(tNode); - const sanitizer = isClassBased ? null : getCurrentStyleSanitizer(); - - // [style.prop] and [class.name] bindings do not use `bind()` and will - // therefore manage accessing and updating the new value in the lView directly. - // For this reason, the checkNoChanges situation must also be handled here - // as well. - if (ngDevMode && getCheckNoChangesMode()) { - const oldValue = getValue(lView, bindingIndex); - if (hasValueChangedUnwrapSafeValue(oldValue, value)) { - const field = isClassBased ? `class.${prop}` : `style.${prop}`; - throwErrorIfNoChangesMode(false, oldValue, value, field); - } - } - - // Direct Apply Case: bypass context resolution and apply the - // style/class value directly to the element - if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) { - const sanitizerToUse = isClassBased ? null : sanitizer; - const renderer = getRenderer(tNode, lView); - updated = applyStylingValueDirectly( - renderer, context, tNode, native, lView, bindingIndex, prop, value, isClassBased, - sanitizerToUse); - - if (sanitizerToUse) { - // it's important we remove the current style sanitizer once the - // element exits, otherwise it will be used by the next styling - // instructions for the next element. - setElementExitFn(stylingApply); - } - } else { - // Context Resolution (or first update) Case: save the value - // and defer to the context to flush and apply the style/class binding - // value to the element. - const directiveIndex = getActiveDirectiveId(); - if (isClassBased) { - updated = updateClassViaContext( - context, tNode, lView, native, directiveIndex, prop, bindingIndex, - value as string | boolean | null, false, firstUpdatePass); - } else { - updated = updateStyleViaContext( - context, tNode, lView, native, directiveIndex, prop, bindingIndex, - value as string | SafeValue | null, sanitizer, false, firstUpdatePass); - } - - setElementExitFn(stylingApply); - } - - return updated; -} /** * Update style bindings using an object literal on an element. @@ -308,319 +195,3 @@ export function ɵɵclassMap(classes: {[className: string]: any} | NO_CHANGE | s classMapInternal(getSelectedIndex(), classes); } -/** - * Internal function for applying a class string or key/value map of classes to an element. - * - * The reason why this function has been separated from `ɵɵclassMap` is because - * it is also called from `ɵɵclassMapInterpolate`. - */ -export function classMapInternal( - elementIndex: number, classes: {[className: string]: any} | NO_CHANGE | string | null): void { - const lView = getLView(); - const tNode = getTNode(elementIndex, lView); - const firstUpdatePass = lView[TVIEW].firstUpdatePass; - const context = getClassesContext(tNode); - const hasDirectiveInput = hasClassInput(tNode); - - // if a value is interpolated then it may render a `NO_CHANGE` value. - // in this case we do not need to do anything, but the binding index - // still needs to be incremented because all styling binding values - // are stored inside of the lView. - const bindingIndex = incrementBindingIndex(2); - const hostBindingsMode = isHostStyling(); - - // inputs are only evaluated from a template binding into a directive, therefore, - // there should not be a situation where a directive host bindings function - // evaluates the inputs (this should only happen in the template function) - if (!hostBindingsMode && hasDirectiveInput && classes !== NO_CHANGE) { - updateDirectiveInputValue(context, lView, tNode, bindingIndex, classes, true, firstUpdatePass); - classes = NO_CHANGE; - } - - // we check for this in the instruction code so that the context can be notified - // about prop or map bindings so that the direct apply check can decide earlier - // if it allows for context resolution to be bypassed. - if (firstUpdatePass) { - patchConfig(tNode, TNodeFlags.hasClassMapBindings); - patchHostStylingFlag(tNode, isHostStyling(), true); - } - - stylingMap( - context, tNode, firstUpdatePass, lView, bindingIndex, classes, true, hasDirectiveInput); -} - -/** - * Shared function used to update a map-based styling binding for an element. - * - * When this function is called it will activate support for `[style]` and - * `[class]` bindings in Angular. - */ -function stylingMap( - context: TStylingContext, tNode: TNode, firstUpdatePass: boolean, lView: LView, - bindingIndex: number, value: {[key: string]: any} | string | null, isClassBased: boolean, - hasDirectiveInput: boolean): void { - const directiveIndex = getActiveDirectiveId(); - const native = getNativeByTNode(tNode, lView) as RElement; - const oldValue = getValue(lView, bindingIndex); - const sanitizer = getCurrentStyleSanitizer(); - const valueHasChanged = hasValueChanged(oldValue, value); - - // [style] and [class] bindings do not use `bind()` and will therefore - // manage accessing and updating the new value in the lView directly. - // For this reason, the checkNoChanges situation must also be handled here - // as well. - if (ngDevMode && valueHasChanged && getCheckNoChangesMode()) { - // check if the value is a StylingMapArray, in which case take the first value (which stores raw - // value) from the array - const previousValue = - isStylingMapArray(oldValue) ? oldValue[StylingMapArrayIndex.RawValuePosition] : oldValue; - throwErrorIfNoChangesMode(false, previousValue, value); - } - - // Direct Apply Case: bypass context resolution and apply the - // style/class map values directly to the element - if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) { - const sanitizerToUse = isClassBased ? null : sanitizer; - const renderer = getRenderer(tNode, lView); - applyStylingMapDirectly( - renderer, context, tNode, native, lView, bindingIndex, value, isClassBased, sanitizerToUse, - valueHasChanged, hasDirectiveInput); - if (sanitizerToUse) { - // it's important we remove the current style sanitizer once the - // element exits, otherwise it will be used by the next styling - // instructions for the next element. - setElementExitFn(stylingApply); - } - } else { - const stylingMapArr = - value === NO_CHANGE ? NO_CHANGE : normalizeIntoStylingMap(oldValue, value, !isClassBased); - - activateStylingMapFeature(); - - // Context Resolution (or first update) Case: save the map value - // and defer to the context to flush and apply the style/class binding - // value to the element. - if (isClassBased) { - updateClassViaContext( - context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, - valueHasChanged, firstUpdatePass); - } else { - updateStyleViaContext( - context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, - sanitizer, valueHasChanged, firstUpdatePass); - } - - setElementExitFn(stylingApply); - } - - if (ngDevMode) { - isClassBased ? ngDevMode.classMap : ngDevMode.styleMap++; - if (valueHasChanged) { - isClassBased ? ngDevMode.classMapCacheMiss : ngDevMode.styleMapCacheMiss++; - } - } -} - -/** - * Writes a value to a directive's `style` or `class` input binding (if it has changed). - * - * If a directive has a `@Input` binding that is set on `style` or `class` then that value - * will take priority over the underlying style/class styling bindings. This value will - * be updated for the binding each time during change detection. - * - * When this occurs this function will attempt to write the value to the input binding - * depending on the following situations: - * - * - If `oldValue !== newValue` - * - If `newValue` is `null` (but this is skipped if it is during the first update pass) - */ -function updateDirectiveInputValue( - context: TStylingContext, lView: LView, tNode: TNode, bindingIndex: number, newValue: any, - isClassBased: boolean, firstUpdatePass: boolean): void { - const oldValue = getValue(lView, bindingIndex); - if (hasValueChanged(oldValue, newValue)) { - // even if the value has changed we may not want to emit it to the - // directive input(s) in the event that it is falsy during the - // first update pass. - if (isStylingValueDefined(newValue) || !firstUpdatePass) { - const inputName: string = isClassBased ? selectClassBasedInputName(tNode.inputs !) : 'style'; - const inputs = tNode.inputs ![inputName] !; - const initialValue = getInitialStylingValue(context); - const value = normalizeStylingDirectiveInputValue(initialValue, newValue, isClassBased); - setInputsForProperty(lView, inputs, inputName, value); - setElementExitFn(stylingApply); - } - setValue(lView, bindingIndex, newValue); - } -} - -/** - * Returns the appropriate directive input value for `style` or `class`. - * - * Earlier versions of Angular expect a binding value to be passed into directive code - * exactly as it is unless there is a static value present (in which case both values - * will be stringified and concatenated). - */ -function normalizeStylingDirectiveInputValue( - initialValue: string, bindingValue: string | {[key: string]: any} | null, - isClassBased: boolean) { - let value = bindingValue; - - // we only concat values if there is an initial value, otherwise we return the value as is. - // Note that this is to satisfy backwards-compatibility in Angular. - if (initialValue.length) { - if (isClassBased) { - value = concatString(initialValue, forceClassesAsString(bindingValue)); - } else { - value = concatString(initialValue, forceStylesAsString(bindingValue, true), ';'); - } - } - return value; -} - -/** - * Flushes all styling code to the element. - * - * This function is designed to be scheduled from any of the four styling instructions - * in this file. When called it will flush all style and class bindings to the element - * via the context resolution algorithm. - */ -function stylingApply(): void { - const lView = getLView(); - const tView = lView[TVIEW]; - const elementIndex = getSelectedIndex(); - const tNode = getTNode(elementIndex, lView); - const native = getNativeByTNode(tNode, lView) as RElement; - const directiveIndex = getActiveDirectiveId(); - const renderer = getRenderer(tNode, lView); - const sanitizer = getCurrentStyleSanitizer(); - const classesContext = isStylingContext(tNode.classes) ? tNode.classes as TStylingContext : null; - const stylesContext = isStylingContext(tNode.styles) ? tNode.styles as TStylingContext : null; - flushStyling( - renderer, lView, tNode, classesContext, stylesContext, native, directiveIndex, sanitizer, - tView.firstUpdatePass); - resetCurrentStyleSanitizer(); -} - -function getRenderer(tNode: TNode, lView: LView) { - return tNode.type === TNodeType.Element ? lView[RENDERER] : null; -} - -/** - * Searches and assigns provided all static style/class entries (found in the `attrs` value) - * and registers them in their respective styling contexts. - */ -export function registerInitialStylingOnTNode( - tNode: TNode, attrs: TAttributes, startIndex: number): boolean { - let hasAdditionalInitialStyling = false; - let styles = getStylingMapArray(tNode.styles); - let classes = getStylingMapArray(tNode.classes); - let mode = -1; - for (let i = startIndex; i < attrs.length; i++) { - const attr = attrs[i] as string; - if (typeof attr == 'number') { - mode = attr; - } else if (mode == AttributeMarker.Classes) { - classes = classes || allocStylingMapArray(null); - addItemToStylingMap(classes, attr, true); - hasAdditionalInitialStyling = true; - } else if (mode == AttributeMarker.Styles) { - const value = attrs[++i] as string | null; - styles = styles || allocStylingMapArray(null); - addItemToStylingMap(styles, attr, value); - hasAdditionalInitialStyling = true; - } - } - - if (classes && classes.length > StylingMapArrayIndex.ValuesStartPosition) { - if (!tNode.classes) { - tNode.classes = classes; - } - updateRawValueOnContext(tNode.classes, stylingMapToString(classes, true)); - } - - if (styles && styles.length > StylingMapArrayIndex.ValuesStartPosition) { - if (!tNode.styles) { - tNode.styles = styles; - } - updateRawValueOnContext(tNode.styles, stylingMapToString(styles, false)); - } - - if (hasAdditionalInitialStyling) { - tNode.flags |= TNodeFlags.hasInitialStyling; - } - - return hasAdditionalInitialStyling; -} - -function updateRawValueOnContext( - context: TStylingContext | StylingMapArray | string, value: string) { - const stylingMapArr = getStylingMapArray(context) !; - stylingMapArr[StylingMapArrayIndex.RawValuePosition] = value; -} - -function getStylesContext(tNode: TNode): TStylingContext { - return getContext(tNode, false); -} - -function getClassesContext(tNode: TNode): TStylingContext { - return getContext(tNode, true); -} - -/** - * Returns/instantiates a styling context from/to a `tNode` instance. - */ -function getContext(tNode: TNode, isClassBased: boolean): TStylingContext { - let context = isClassBased ? tNode.classes : tNode.styles; - if (!isStylingContext(context)) { - const hasDirectives = isDirectiveHost(tNode); - context = allocTStylingContext(context as StylingMapArray | null, hasDirectives); - if (ngDevMode) { - attachStylingDebugObject(context as TStylingContext, tNode, isClassBased); - } - - if (isClassBased) { - tNode.classes = context; - } else { - tNode.styles = context; - } - } - return context as TStylingContext; -} - -function resolveStylePropValue( - value: string | number | SafeValue | null | NO_CHANGE, - suffix: string | null | undefined): string|SafeValue|null|undefined|NO_CHANGE { - if (value === NO_CHANGE) return value; - - let resolvedValue: string|null = null; - if (isStylingValueDefined(value)) { - if (suffix) { - // when a suffix is applied then it will bypass - // sanitization entirely (b/c a new string is created) - resolvedValue = renderStringify(value) + suffix; - } else { - // sanitization happens by dealing with a string value - // this means that the string value will be passed through - // into the style rendering later (which is where the value - // will be sanitized before it is applied) - resolvedValue = value as any as string; - } - } - return resolvedValue; -} - -/** - * Whether or not the style/class binding being applied was executed within a host bindings - * function. - */ -function isHostStyling(): boolean { - return isHostStylingActive(getActiveDirectiveId()); -} - -function patchHostStylingFlag(tNode: TNode, hostBindingsMode: boolean, isClassBased: boolean) { - const flag = hostBindingsMode ? - isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings : - isClassBased ? TNodeFlags.hasTemplateClassBindings : TNodeFlags.hasTemplateStyleBindings; - patchConfig(tNode, flag); -} diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 1e51ca2594..cad0ce8c71 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -99,15 +99,6 @@ interface LFrame { */ currentDirectiveDef: DirectiveDef|ComponentDef|null; - /** - * Used as the starting directive id value. - * - * All subsequent directives are incremented from this value onwards. - * The reason why this value is `1` instead of `0` is because the `0` - * value is reserved for the template. - */ - activeDirectiveId: number; - /** * The root index from which pure function instructions should calculate their binding * indices. In component views, this is TView.bindingStartIndex. In a host binding @@ -273,12 +264,6 @@ export const enum ActiveElementFlags { Size = 1, } -/** - * Determines whether or not a flag is currently set for the active element. - */ -export function hasActiveElementFlag(flag: ActiveElementFlags) { - return (instructionState.lFrame.selectedIndex & flag) === flag; -} /** * Sets a flag is for the active element. @@ -329,54 +314,6 @@ export function setElementExitFn(fn: () => void): void { assertEqual(instructionState.elementExitFn, fn, 'Expecting to always get the same function'); } -/** - * Returns the current id value of the current directive. - * - * For example we have an element that has two directives on it: - *
- * - * dirOne->hostBindings() (id == 1) - * dirTwo->hostBindings() (id == 2) - * - * Note that this is only active when `hostBinding` functions are being processed. - * - * Note that directive id values are specific to an element (this means that - * the same id value could be present on another element with a completely - * different set of directives). - */ -export function getActiveDirectiveId() { - return instructionState.lFrame.activeDirectiveId; -} - -/** - * Increments the current directive id value. - * - * For example we have an element that has two directives on it: - *
- * - * dirOne->hostBindings() (index = 1) - * // increment - * dirTwo->hostBindings() (index = 2) - * - * Depending on whether or not a previous directive had any inherited - * directives present, that value will be incremented in addition - * to the id jumping up by one. - * - * Note that this is only active when `hostBinding` functions are being processed. - * - * Note that directive id values are specific to an element (this means that - * the same id value could be present on another element with a completely - * different set of directives). - */ -export function incrementActiveDirectiveId() { - // Each directive gets a uniqueId value that is the same for both - // create and update calls when the hostBindings function is called. The - // directive uniqueId is not set anywhere--it is just incremented between - // each hostBindings call and is useful for helping instruction code - // uniquely determine which directive is currently active when executed. - instructionState.lFrame.activeDirectiveId += 1; -} - /** * Restores `contextViewData` to the given OpaqueViewState instance. * diff --git a/packages/core/src/render3/styling/bindings.ts b/packages/core/src/render3/styling/bindings.ts deleted file mode 100644 index b3a120ae09..0000000000 --- a/packages/core/src/render3/styling/bindings.ts +++ /dev/null @@ -1,1108 +0,0 @@ -/** -* @license -* Copyright Google Inc. All Rights Reserved. -* -* Use of this source code is governed by an MIT-style license that can be -* found in the LICENSE file at https://angular.io/license -*/ -import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass'; -import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer'; -import {global} from '../../util/global'; -import {TNodeFlags} from '../interfaces/node'; -import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer'; -import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags, TStylingNode} from '../interfaces/styling'; -import {NO_CHANGE} from '../tokens'; -import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE, MAP_BASED_ENTRY_PROP_NAME, TEMPLATE_DIRECTIVE_INDEX, concatString, forceStylesAsString, getBindingValue, getDefaultValue, getGuardMask, getInitialStylingValue, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getTotalSources, getValue, getValuesCount, hasConfig, hasValueChanged, isHostStylingActive, isSanitizationRequired, isStylingMapArray, isStylingValueDefined, normalizeIntoStylingMap, patchConfig, setDefaultValue, setGuardMask, setMapAsDirty, setValue} from '../util/styling_utils'; - -import {getStylingState, resetStylingState} from './state'; - -const VALUE_IS_EXTERNALLY_MODIFIED = {}; - -/** - * -------- - * - * This file contains the core logic for styling in Angular. - * - * All styling bindings (i.e. `[style]`, `[style.prop]`, `[class]` and `[class.name]`) - * will have their values be applied through the logic in this file. - * - * When a binding is encountered (e.g. `
`) then - * the binding data will be populated into a `TStylingContext` data-structure. - * There is only one `TStylingContext` per `TStylingNode` and each element instance - * will update its style/class binding values in concert with the styling - * context. - * - * To learn more about the algorithm see `TStylingContext`. - * - * -------- - */ - -/** - * The guard/update mask bit index location for map-based bindings. - * - * All map-based bindings (i.e. `[style]` and `[class]` ) - */ -const STYLING_INDEX_FOR_MAP_BINDING = 0; - -/** - * Visits a class-based binding and updates the new value (if changed). - * - * This function is called each time a class-based styling instruction - * is executed. It's important that it's always called (even if the value - * has not changed) so that the inner counter index value is incremented. - * This way, each instruction is always guaranteed to get the same counter - * state each time it's called (which then allows the `TStylingContext` - * and the bit mask values to be in sync). - */ -export function updateClassViaContext( - context: TStylingContext, tNode: TStylingNode, data: LStylingData, element: RElement, - directiveIndex: number, prop: string | null, bindingIndex: number, - value: boolean | string | null | undefined | StylingMapArray | NO_CHANGE, forceUpdate: boolean, - firstUpdatePass: boolean): boolean { - const isMapBased = !prop; - const state = getStylingState(element, directiveIndex); - const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.classesIndex++; - - // even if the initial value is a `NO_CHANGE` value (e.g. interpolation or [ngClass]) - // then we still need to register the binding within the context so that the context - // is aware of the binding even if things change after the first update pass. - if (firstUpdatePass || value !== NO_CHANGE) { - const updated = updateBindingData( - context, tNode, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, - false, firstUpdatePass, true); - if (updated || forceUpdate) { - // We flip the bit in the bitMask to reflect that the binding - // at the `index` slot has changed. This identifies to the flushing - // phase that the bindings for this particular CSS class need to be - // applied again because on or more of the bindings for the CSS - // class have changed. - state.classesBitMask |= 1 << countIndex; - return true; - } - } - return false; -} - -/** - * Visits a style-based binding and updates the new value (if changed). - * - * This function is called each time a style-based styling instruction - * is executed. It's important that it's always called (even if the value - * has not changed) so that the inner counter index value is incremented. - * This way, each instruction is always guaranteed to get the same counter - * state each time it's called (which then allows the `TStylingContext` - * and the bit mask values to be in sync). - */ -export function updateStyleViaContext( - context: TStylingContext, tNode: TStylingNode, data: LStylingData, element: RElement, - directiveIndex: number, prop: string | null, bindingIndex: number, - value: string | number | SafeValue | null | undefined | StylingMapArray | NO_CHANGE, - sanitizer: StyleSanitizeFn | null, forceUpdate: boolean, firstUpdatePass: boolean): boolean { - const isMapBased = !prop; - const state = getStylingState(element, directiveIndex); - const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++; - - // even if the initial value is a `NO_CHANGE` value (e.g. interpolation or [ngStyle]) - // then we still need to register the binding within the context so that the context - // is aware of the binding even if things change after the first update pass. - if (firstUpdatePass || value !== NO_CHANGE) { - const sanitizationRequired = isMapBased ? - true : - (sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false); - const updated = updateBindingData( - context, tNode, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, - sanitizationRequired, firstUpdatePass, false); - if (updated || forceUpdate) { - // We flip the bit in the bitMask to reflect that the binding - // at the `index` slot has changed. This identifies to the flushing - // phase that the bindings for this particular property need to be - // applied again because on or more of the bindings for the CSS - // property have changed. - state.stylesBitMask |= 1 << countIndex; - return true; - } - } - return false; -} - -/** - * Called each time a binding value has changed within the provided `TStylingContext`. - * - * This function is designed to be called from `updateStyleBinding` and `updateClassBinding`. - * If called during the first update pass, the binding will be registered in the context. - * - * This function will also update binding slot in the provided `LStylingData` with the - * new binding entry (if it has changed). - * - * @returns whether or not the binding value was updated in the `LStylingData`. - */ -function updateBindingData( - context: TStylingContext, tNode: TStylingNode, data: LStylingData, counterIndex: number, - sourceIndex: number, prop: string | null, bindingIndex: number, - value: string | SafeValue | number | boolean | null | undefined | StylingMapArray, - forceUpdate: boolean, sanitizationRequired: boolean, firstUpdatePass: boolean, - isClassBased: boolean): boolean { - const hostBindingsMode = isHostStylingActive(sourceIndex); - const hostBindingsFlag = - isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings; - if (firstUpdatePass) { - // this will only happen during the first update pass of the - // context. The reason why we can't use `tView.firstCreatePass` - // here is because its not guaranteed to be true when the first - // update pass is executed (remember that all styling instructions - // are run in the update phase, and, as a result, are no more - // styling instructions that are run in the creation phase). - registerBinding( - context, tNode, counterIndex, sourceIndex, prop, bindingIndex, sanitizationRequired, - isClassBased); - } - - const changed = forceUpdate || hasValueChanged(data[bindingIndex], value); - if (changed) { - setValue(data, bindingIndex, value); - const doSetValuesAsStale = - hasConfig(tNode, hostBindingsFlag) && !hostBindingsMode && (prop ? !value : true); - if (doSetValuesAsStale) { - renderHostBindingsAsStale(context, tNode, data, prop, isClassBased); - } - } - return changed; -} - -/** - * Iterates over all host-binding values for the given `prop` value in the context and sets their - * corresponding binding values to `null`. - * - * Whenever a template binding changes its value to `null`, all host-binding values should be - * re-applied - * to the element when the host bindings are evaluated. This may not always happen in the event - * that none of the bindings changed within the host bindings code. For this reason this function - * is expected to be called each time a template binding becomes falsy or when a map-based template - * binding changes. - */ -function renderHostBindingsAsStale( - context: TStylingContext, tNode: TStylingNode, data: LStylingData, prop: string | null, - isClassBased: boolean): void { - const valuesCount = getValuesCount(context); - - const hostBindingsFlag = - isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings; - if (prop !== null && hasConfig(tNode, hostBindingsFlag)) { - const itemsPerRow = TStylingContextIndex.BindingsStartOffset + valuesCount; - - let i = TStylingContextIndex.ValuesStartPosition; - let found = false; - while (i < context.length) { - if (getProp(context, i) === prop) { - found = true; - break; - } - i += itemsPerRow; - } - - if (found) { - const bindingsStart = i + TStylingContextIndex.BindingsStartOffset; - const valuesStart = bindingsStart + 1; // the first column is template bindings - const valuesEnd = bindingsStart + valuesCount - 1; - - for (let i = valuesStart; i < valuesEnd; i++) { - const bindingIndex = context[i] as number; - if (bindingIndex !== 0) { - setValue(data, bindingIndex, null); - } - } - } - } - - const mapBindingsFlag = - isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; - if (hasConfig(tNode, mapBindingsFlag)) { - const bindingsStart = - TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset; - const valuesStart = bindingsStart + 1; // the first column is template bindings - const valuesEnd = bindingsStart + valuesCount - 1; - for (let i = valuesStart; i < valuesEnd; i++) { - const stylingMap = getValue(data, context[i] as number); - if (stylingMap) { - setMapAsDirty(stylingMap); - } - } - } -} - -/** - * Registers the provided binding (prop + bindingIndex) into the context. - * - * It is needed because it will either update or insert a styling property - * into the context at the correct spot. - * - * When called, one of two things will happen: - * - * 1) If the property already exists in the context then it will just add - * the provided `bindingValue` to the end of the binding sources region - * for that particular property. - * - * - If the binding value is a number then it will be added as a new - * binding index source next to the other binding sources for the property. - * - * - Otherwise, if the binding value is a string/boolean/null type then it will - * replace the default value for the property if the default value is `null`. - * - * 2) If the property does not exist then it will be inserted into the context. - * The styling context relies on all properties being stored in alphabetical - * order, so it knows exactly where to store it. - * - * When inserted, a default `null` value is created for the property which exists - * as the default value for the binding. If the bindingValue property is inserted - * and it is either a string, number or null value then that will replace the default - * value. - * - * Note that this function is also used for map-based styling bindings. They are treated - * much the same as prop-based bindings, but, their property name value is set as `[MAP]`. - */ -export function registerBinding( - context: TStylingContext, tNode: TStylingNode, countId: number, sourceIndex: number, - prop: string | null, bindingValue: number | null | string | boolean, - sanitizationRequired: boolean, isClassBased: boolean): void { - let found = false; - prop = prop || MAP_BASED_ENTRY_PROP_NAME; - - let totalSources = getTotalSources(context); - - // if a new source is detected then a new column needs to be allocated into - // the styling context. The column is basically a new allocation of binding - // sources that will be available to each property. - while (totalSources <= sourceIndex) { - addNewSourceColumn(context); - totalSources++; - } - - const collisionFlag = - isClassBased ? TNodeFlags.hasDuplicateClassBindings : TNodeFlags.hasDuplicateStyleBindings; - const isBindingIndexValue = typeof bindingValue === 'number'; - const entriesPerRow = TStylingContextIndex.BindingsStartOffset + getValuesCount(context); - let i = TStylingContextIndex.ValuesStartPosition; - - // all style/class bindings are sorted by property name - while (i < context.length) { - const p = getProp(context, i); - if (prop <= p) { - if (prop < p) { - allocateNewContextEntry(context, i, prop, sanitizationRequired); - } else if (isBindingIndexValue) { - patchConfig(tNode, collisionFlag); - } - addBindingIntoContext(context, i, bindingValue, countId, sourceIndex); - found = true; - break; - } - i += entriesPerRow; - } - - if (!found) { - allocateNewContextEntry(context, context.length, prop, sanitizationRequired); - addBindingIntoContext(context, i, bindingValue, countId, sourceIndex); - } -} - -/** - * Inserts a new row into the provided `TStylingContext` and assigns the provided `prop` value as - * the property entry. - */ -function allocateNewContextEntry( - context: TStylingContext, index: number, prop: string, sanitizationRequired?: boolean): void { - const config = sanitizationRequired ? TStylingContextPropConfigFlags.SanitizationRequired : - TStylingContextPropConfigFlags.Default; - context.splice( - index, 0, - config, // 1) config value - DEFAULT_GUARD_MASK_VALUE, // 2) template bit mask - DEFAULT_GUARD_MASK_VALUE, // 3) host bindings bit mask - prop, // 4) prop value (e.g. `width`, `myClass`, etc...) - ); - - index += 4; // the 4 values above - - // 5...) default binding index for the template value - // depending on how many sources already exist in the context, - // multiple default index entries may need to be inserted for - // the new value in the context. - const totalBindingsPerEntry = getTotalSources(context); - for (let i = 0; i < totalBindingsPerEntry; i++) { - context.splice(index, 0, DEFAULT_BINDING_INDEX); - index++; - } - - // 6) default binding value for the new entry - context.splice(index, 0, DEFAULT_BINDING_VALUE); -} - -/** - * Inserts a new binding value into a styling property tuple in the `TStylingContext`. - * - * A bindingValue is inserted into a context during the first update pass - * of a template or host bindings function. When this occurs, two things - * happen: - * - * - If the bindingValue value is a number then it is treated as a bindingIndex - * value (a index in the `LView`) and it will be inserted next to the other - * binding index entries. - * - * - Otherwise the binding value will update the default value for the property - * and this will only happen if the default value is `null`. - */ -function addBindingIntoContext( - context: TStylingContext, index: number, bindingValue: number | string | boolean | null, - bitIndex: number, sourceIndex: number) { - if (typeof bindingValue === 'number') { - const hostBindingsMode = isHostStylingActive(sourceIndex); - const cellIndex = index + TStylingContextIndex.BindingsStartOffset + sourceIndex; - context[cellIndex] = bindingValue; - const updatedBitMask = getGuardMask(context, index, hostBindingsMode) | (1 << bitIndex); - setGuardMask(context, index, updatedBitMask, hostBindingsMode); - } else if (bindingValue !== null && getDefaultValue(context, index) === null) { - setDefaultValue(context, index, bindingValue); - } -} - -/** - * Registers a new column into the provided `TStylingContext`. - * - * If and when a new source is detected then a new column needs to - * be allocated into the styling context. The column is basically - * a new allocation of binding sources that will be available to each - * property. - * - * Each column that exists in the styling context resembles a styling - * source. A styling source an either be the template or one or more - * components or directives all containing styling host bindings. - */ -function addNewSourceColumn(context: TStylingContext): void { - // we use -1 here because we want to insert right before the last value (the default value) - const insertOffset = TStylingContextIndex.BindingsStartOffset + getValuesCount(context) - 1; - - let index = TStylingContextIndex.ValuesStartPosition; - while (index < context.length) { - index += insertOffset; - context.splice(index++, 0, DEFAULT_BINDING_INDEX); - - // the value was inserted just before the default value, but the - // next entry in the context starts just after it. Therefore++. - index++; - } - context[TStylingContextIndex.TotalSourcesPosition]++; -} - -/** - * Applies all pending style and class bindings to the provided element. - * - * This function will attempt to flush styling via the provided `classesContext` - * and `stylesContext` context values. This function is designed to be run from - * the internal `stylingApply` function (which is scheduled to run at the very - * end of change detection for an element if one or more style/class bindings - * were processed) and will rely on any state values that are set from when - * any of the styling bindings executed. - * - * This function is designed to be called twice: one when change detection has - * processed an element within the template bindings (i.e. just as `advance()` - * is called) and when host bindings have been processed. In both cases the - * styles and classes in both contexts will be applied to the element, but the - * algorithm will selectively decide which bindings to run depending on the - * columns in the context. The provided `directiveIndex` value will help the - * algorithm determine which bindings to apply: either the template bindings or - * the host bindings (see `applyStylingToElement` for more information). - * - * Note that once this function is called all temporary styling state data - * (i.e. the `bitMask` and `counter` values for styles and classes will be cleared). - */ -export function flushStyling( - renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, tNode: TStylingNode, - classesContext: TStylingContext | null, stylesContext: TStylingContext | null, - element: RElement, directiveIndex: number, styleSanitizer: StyleSanitizeFn | null, - firstUpdatePass: boolean): void { - ngDevMode && ngDevMode.flushStyling++; - - const state = getStylingState(element, directiveIndex); - const hostBindingsMode = isHostStylingActive(state.sourceIndex); - - if (stylesContext) { - firstUpdatePass && syncContextInitialStyling(stylesContext, tNode, false); - - if (state.stylesBitMask !== 0) { - applyStylingViaContext( - stylesContext, tNode, renderer, element, data, state.stylesBitMask, setStyle, - styleSanitizer, hostBindingsMode, false); - } - } - - if (classesContext) { - firstUpdatePass && syncContextInitialStyling(classesContext, tNode, true); - - if (state.classesBitMask !== 0) { - applyStylingViaContext( - classesContext, tNode, renderer, element, data, state.classesBitMask, setClass, null, - hostBindingsMode, true); - } - } - - resetStylingState(); -} - -/** - * Registers all static styling values into the context as default values. - * - * Static styles are stored on the `tNode.styles` and `tNode.classes` - * properties as instances of `StylingMapArray`. When an instance of - * `TStylingContext` is assigned to `tNode.styles` and `tNode.classes` - * then the existing initial styling values are copied into the the - * `InitialStylingValuePosition` slot. - * - * Because all static styles/classes are collected and registered on - * the initial styling array each time a directive is instantiated, - * the context may not yet know about the static values. When this - * function is called it will copy over all the static style/class - * values from the initial styling array into the context as default - * values for each of the matching entries in the context. - * - * Let's imagine the following example: - * - * ```html - *
- * ... - *
- * ``` - * - * When the code above is processed, the underlying element/styling - * instructions will create an instance of `TStylingContext` for - * the `tNode.styles` property. Here's what that looks like: - * - * ```typescript - * tNode.styles = [ - * // ... - * // initial styles - * ['color:red; height:200px', 'color', 'red', 'height', '200px'], - * - * 0, 0b1, 0b0, 'color', 20, null, // [style.color] binding - * ] - * ``` - * - * After this function is called it will balance out the context with - * the static `color` and `height` values and set them as defaults within - * the context: - * - * ```typescript - * tNode.styles = [ - * // ... - * // initial styles - * ['color:red; height:200px', 'color', 'red', 'height', '200px'], - * - * 0, 0b1, 0b0, 'color', 20, 'red', - * 0, 0b0, 0b0, 'height', 0, '200px', - * ] - * ``` - */ -function syncContextInitialStyling( - context: TStylingContext, tNode: TStylingNode, isClassBased: boolean): void { - // the TStylingContext always has initial style/class values which are - // stored in styling array format. - updateInitialStylingOnContext(context, tNode, getStylingMapArray(context) !, isClassBased); -} - -/** - * Registers all initial styling entries into the provided context. - * - * This function will iterate over all entries in the provided `initialStyling` ar}ray and register - * them as default (initial) values in the provided context. Initial styling values in a context are - * the default values that are to be applied unless overwritten by a binding. - * - * The reason why this function exists and isn't a part of the context construction is because - * host binding is evaluated at a later stage after the element is created. This means that - * if a directive or component contains any initial styling code (i.e. `
`) - * then that initial styling data can only be applied once the styling for that element - * is first applied (at the end of the update phase). Once that happens then the context will - * update itself with the complete initial styling for the element. - */ -function updateInitialStylingOnContext( - context: TStylingContext, tNode: TStylingNode, initialStyling: StylingMapArray, - isClassBased: boolean): void { - // `-1` is used here because all initial styling data is not a apart - // of a binding (since it's static) - const COUNT_ID_FOR_STYLING = -1; - - let hasInitialStyling = false; - for (let i = StylingMapArrayIndex.ValuesStartPosition; i < initialStyling.length; - i += StylingMapArrayIndex.TupleSize) { - const value = getMapValue(initialStyling, i); - if (value) { - const prop = getMapProp(initialStyling, i); - registerBinding(context, tNode, COUNT_ID_FOR_STYLING, 0, prop, value, false, isClassBased); - hasInitialStyling = true; - } - } - - if (hasInitialStyling) { - patchConfig(tNode, TNodeFlags.hasInitialStyling); - } -} - -/** - * Runs through the provided styling context and applies each value to - * the provided element (via the renderer) if one or more values are present. - * - * This function will iterate over all entries present in the provided - * `TStylingContext` array (both prop-based and map-based bindings).- - * - * Each entry, within the `TStylingContext` array, is stored alphabetically - * and this means that each prop/value entry will be applied in order - * (so long as it is marked dirty in the provided `bitMask` value). - * - * If there are any map-based entries present (which are applied to the - * element via the `[style]` and `[class]` bindings) then those entries - * will be applied as well. However, the code for that is not a part of - * this function. Instead, each time a property is visited, then the - * code below will call an external function called `stylingMapsSyncFn` - * and, if present, it will keep the application of styling values in - * map-based bindings up to sync with the application of prop-based - * bindings. - * - * Visit `styling/map_based_bindings.ts` to learn more about how the - * algorithm works for map-based styling bindings. - * - * Note that this function is not designed to be called in isolation (use - * the `flushStyling` function so that it can call this function for both - * the styles and classes contexts). - */ -export function applyStylingViaContext( - context: TStylingContext, tNode: TStylingNode, renderer: Renderer3 | ProceduralRenderer3 | null, - element: RElement, bindingData: LStylingData, bitMaskValue: number | boolean, - applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null, hostBindingsMode: boolean, - isClassBased: boolean): void { - const bitMask = normalizeBitMaskValue(bitMaskValue); - - let stylingMapsSyncFn: SyncStylingMapsFn|null = null; - let applyAllValues = false; - const mapBindingsFlag = - isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; - if (hasConfig(tNode, mapBindingsFlag)) { - stylingMapsSyncFn = getStylingMapsSyncFn(); - const mapsGuardMask = - getGuardMask(context, TStylingContextIndex.ValuesStartPosition, hostBindingsMode); - applyAllValues = (bitMask & mapsGuardMask) !== 0; - } - - const valuesCount = getValuesCount(context); - let totalBindingsToVisit = 1; - let mapsMode = - applyAllValues ? StylingMapsSyncMode.ApplyAllValues : StylingMapsSyncMode.TraverseValues; - if (hostBindingsMode) { - mapsMode |= StylingMapsSyncMode.RecurseInnerMaps; - totalBindingsToVisit = valuesCount - 1; - } - - let i = getPropValuesStartPosition(context, tNode, isClassBased); - while (i < context.length) { - const guardMask = getGuardMask(context, i, hostBindingsMode); - if (bitMask & guardMask) { - let valueApplied = false; - const prop = getProp(context, i); - const defaultValue = getDefaultValue(context, i); - - // Part 1: Visit the `[styling.prop]` value - for (let j = 0; j < totalBindingsToVisit; j++) { - const bindingIndex = getBindingValue(context, i, j) as number; - if (!valueApplied && bindingIndex !== 0) { - const value = getValue(bindingData, bindingIndex); - if (isStylingValueDefined(value)) { - const checkValueOnly = hostBindingsMode && j === 0; - if (!checkValueOnly) { - const finalValue = sanitizer && isSanitizationRequired(context, i) ? - sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) : - unwrapSafeValue(value); - applyStylingFn(renderer, element, prop, finalValue, bindingIndex); - } - valueApplied = true; - } - } - - // Part 2: Visit the `[style]` or `[class]` map-based value - if (stylingMapsSyncFn) { - // determine whether or not to apply the target property or to skip it - let mode = mapsMode | (valueApplied ? StylingMapsSyncMode.SkipTargetProp : - StylingMapsSyncMode.ApplyTargetProp); - - // the first column in the context (when `j == 0`) is special-cased for - // template bindings. If and when host bindings are being processed then - // the first column will still be iterated over, but the values will only - // be checked against (not applied). If and when this happens we need to - // notify the map-based syncing code to know not to apply the values it - // comes across in the very first map-based binding (which is also located - // in column zero). - if (hostBindingsMode && j === 0) { - mode |= StylingMapsSyncMode.CheckValuesOnly; - } - - const valueAppliedWithinMap = stylingMapsSyncFn( - context, renderer, element, bindingData, j, applyStylingFn, sanitizer, mode, prop, - defaultValue); - valueApplied = valueApplied || valueAppliedWithinMap; - } - } - - // Part 3: apply the default value (e.g. `
` => `200px` gets applied) - // if the value has not yet been applied then a truthy value does not exist in the - // prop-based or map-based bindings code. If and when this happens, just apply the - // default value (even if the default value is `null`). - if (!valueApplied) { - applyStylingFn(renderer, element, prop, defaultValue); - } - } - - i += TStylingContextIndex.BindingsStartOffset + valuesCount; - } - - // the map-based styling entries may have not applied all their - // values. For this reason, one more call to the sync function - // needs to be issued at the end. - if (stylingMapsSyncFn) { - if (hostBindingsMode) { - mapsMode |= StylingMapsSyncMode.CheckValuesOnly; - } - stylingMapsSyncFn( - context, renderer, element, bindingData, 0, applyStylingFn, sanitizer, mapsMode); - } -} - -/** - * Applies the provided styling map to the element directly (without context resolution). - * - * This function is designed to be run from the styling instructions and will be called - * automatically. This function is intended to be used for performance reasons in the - * event that there is no need to apply styling via context resolution. - * - * This function has three different cases that can occur (for each item in the map): - * - * - Case 1: Attempt to apply the current value in the map to the element (if it's `non null`). - * - * - Case 2: If a map value fails to be applied then the algorithm will find a matching entry in - * the initial values present in the context and attempt to apply that. - * - * - Default Case: If the initial value cannot be applied then a default value of `null` will be - * applied (which will remove the style/class value from the element). - * - * See `allowDirectStylingApply` to learn the logic used to determine whether any style/class - * bindings can be directly applied. - * - * @returns whether or not the styling map was applied to the element. - */ -export function applyStylingMapDirectly( - renderer: any, context: TStylingContext, tNode: TStylingNode, element: RElement, - data: LStylingData, bindingIndex: number, value: {[key: string]: any} | string | null, - isClassBased: boolean, sanitizer: StyleSanitizeFn | null, forceUpdate: boolean, - bindingValueContainsInitial: boolean): void { - const oldValue = getValue(data, bindingIndex); - if (forceUpdate || hasValueChanged(oldValue, value)) { - const hasInitial = hasConfig(tNode, TNodeFlags.hasInitialStyling); - const initialValue = - hasInitial && !bindingValueContainsInitial ? getInitialStylingValue(context) : null; - setValue(data, bindingIndex, value); - - // the cached value is the last snapshot of the style or class - // attribute value and is used in the if statement below to - // keep track of internal/external changes. - const cachedValueIndex = bindingIndex + 1; - let cachedValue = getValue(data, cachedValueIndex); - if (cachedValue === NO_CHANGE) { - cachedValue = initialValue; - } - cachedValue = typeof cachedValue !== 'string' ? '' : cachedValue; - - // If a class/style value was modified externally then the styling - // fast pass cannot guarantee that the external values are retained. - // When this happens, the algorithm will bail out and not write to - // the style or className attribute directly. - const propBindingsFlag = - isClassBased ? TNodeFlags.hasClassPropBindings : TNodeFlags.hasStylePropBindings; - let writeToAttrDirectly = !hasConfig(tNode, propBindingsFlag); - if (writeToAttrDirectly && - checkIfExternallyModified(element as HTMLElement, cachedValue, isClassBased)) { - writeToAttrDirectly = false; - if (oldValue !== VALUE_IS_EXTERNALLY_MODIFIED) { - // direct styling will reset the attribute entirely each time, - // and, for this reason, if the algorithm decides it cannot - // write to the class/style attributes directly then it must - // reset all the previous style/class values before it starts - // to apply values in the non-direct way. - removeStylingValues(renderer, element, oldValue, isClassBased); - - // this will instruct the algorithm not to apply class or style - // values directly anymore. - setValue(data, cachedValueIndex, VALUE_IS_EXTERNALLY_MODIFIED); - } - } - - if (writeToAttrDirectly) { - const initialValue = - hasInitial && !bindingValueContainsInitial ? getInitialStylingValue(context) : null; - const valueToApply = - writeStylingValueDirectly(renderer, element, value, isClassBased, initialValue); - setValue(data, cachedValueIndex, valueToApply || null); - } else { - const applyFn = isClassBased ? setClass : setStyle; - const map = normalizeIntoStylingMap(oldValue, value, !isClassBased); - const initialStyles = hasInitial ? getStylingMapArray(context) : null; - - for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length; - i += StylingMapArrayIndex.TupleSize) { - const prop = getMapProp(map, i); - const value = getMapValue(map, i); - - // case 1: apply the map value (if it exists) - let applied = - applyStylingValue(renderer, element, prop, value, applyFn, bindingIndex, sanitizer); - - // case 2: apply the initial value (if it exists) - if (!applied && initialStyles) { - applied = findAndApplyMapValue( - renderer, element, applyFn, initialStyles, prop, bindingIndex, sanitizer); - } - - // default case: apply `null` to remove the value - if (!applied) { - applyFn(renderer, element, prop, null, bindingIndex); - } - } - - const state = getStylingState(element, TEMPLATE_DIRECTIVE_INDEX); - if (isClassBased) { - state.lastDirectClassMap = map; - } else { - state.lastDirectStyleMap = map; - } - } - } -} - -export function writeStylingValueDirectly( - renderer: any, element: RElement, value: {[key: string]: any} | string | null, - isClassBased: boolean, initialValue: string | null): string { - let valueToApply: string; - if (isClassBased) { - valueToApply = typeof value === 'string' ? value : objectToClassName(value); - if (initialValue !== null) { - valueToApply = concatString(initialValue, valueToApply, ' '); - } - setClassName(renderer, element, valueToApply); - } else { - valueToApply = forceStylesAsString(value, true); - if (initialValue !== null) { - valueToApply = initialValue + ';' + valueToApply; - } - setStyleAttr(renderer, element, valueToApply); - } - return valueToApply; -} - -/** - * Applies the provided styling prop/value to the element directly (without context resolution). - * - * This function is designed to be run from the styling instructions and will be called - * automatically. This function is intended to be used for performance reasons in the - * event that there is no need to apply styling via context resolution. - * - * This function has four different cases that can occur: - * - * - Case 1: Apply the provided prop/value (style or class) entry to the element - * (if it is `non null`). - * - * - Case 2: If value does not get applied (because its `null` or `undefined`) then the algorithm - * will check to see if a styling map value was applied to the element as well just - * before this (via `styleMap` or `classMap`). If and when a map is present then the - * algorithm will find the matching property in the map and apply its value. - * - * - Case 3: If a map value fails to be applied then the algorithm will check to see if there - * are any initial values present and attempt to apply a matching value based on - * the target prop. - * - * - Default Case: If a matching initial value cannot be applied then a default value - * of `null` will be applied (which will remove the style/class value - * from the element). - * - * See `allowDirectStylingApply` to learn the logic used to determine whether any style/class - * bindings can be directly applied. - * - * @returns whether or not the prop/value styling was applied to the element. - */ -export function applyStylingValueDirectly( - renderer: any, context: TStylingContext, tNode: TStylingNode, element: RElement, - data: LStylingData, bindingIndex: number, prop: string, value: any, isClassBased: boolean, - sanitizer?: StyleSanitizeFn | null): boolean { - let applied = false; - if (hasValueChanged(data[bindingIndex], value)) { - setValue(data, bindingIndex, value); - const applyFn = isClassBased ? setClass : setStyle; - - // case 1: apply the provided value (if it exists) - applied = applyStylingValue(renderer, element, prop, value, applyFn, bindingIndex, sanitizer); - - // case 2: find the matching property in a styling map and apply the detected value - const mapBindingsFlag = - isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; - if (!applied && hasConfig(tNode, mapBindingsFlag)) { - const state = getStylingState(element, TEMPLATE_DIRECTIVE_INDEX); - const map = isClassBased ? state.lastDirectClassMap : state.lastDirectStyleMap; - applied = map ? - findAndApplyMapValue(renderer, element, applyFn, map, prop, bindingIndex, sanitizer) : - false; - } - - // case 3: apply the initial value (if it exists) - if (!applied && hasConfig(tNode, TNodeFlags.hasInitialStyling)) { - const map = getStylingMapArray(context); - applied = - map ? findAndApplyMapValue(renderer, element, applyFn, map, prop, bindingIndex) : false; - } - - // default case: apply `null` to remove the value - if (!applied) { - applyFn(renderer, element, prop, null, bindingIndex); - } - } - return applied; -} - -function applyStylingValue( - renderer: any, element: RElement, prop: string, value: any, applyFn: ApplyStylingFn, - bindingIndex: number, sanitizer?: StyleSanitizeFn | null): boolean { - let valueToApply: string|null = unwrapSafeValue(value); - if (isStylingValueDefined(valueToApply)) { - valueToApply = - sanitizer ? sanitizer(prop, value, StyleSanitizeMode.ValidateAndSanitize) : valueToApply; - applyFn(renderer, element, prop, valueToApply, bindingIndex); - return true; - } - return false; -} - -function findAndApplyMapValue( - renderer: any, element: RElement, applyFn: ApplyStylingFn, map: StylingMapArray, prop: string, - bindingIndex: number, sanitizer?: StyleSanitizeFn | null) { - for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length; - i += StylingMapArrayIndex.TupleSize) { - const p = getMapProp(map, i); - if (p === prop) { - let valueToApply = getMapValue(map, i); - valueToApply = sanitizer ? - sanitizer(prop, valueToApply, StyleSanitizeMode.ValidateAndSanitize) : - valueToApply; - applyFn(renderer, element, prop, valueToApply, bindingIndex); - return true; - } - if (p > prop) { - break; - } - } - return false; -} - -function normalizeBitMaskValue(value: number | boolean): number { - // if pass => apply all values (-1 implies that all bits are flipped to true) - if (value === true) return -1; - - // if pass => skip all values - if (value === false) return 0; - - // return the bit mask value as is - return value; -} - -let _activeStylingMapApplyFn: SyncStylingMapsFn|null = null; -export function getStylingMapsSyncFn() { - return _activeStylingMapApplyFn; -} - -export function setStylingMapsSyncFn(fn: SyncStylingMapsFn) { - _activeStylingMapApplyFn = fn; -} - -/** - * Assigns a style value to a style property for the given element. - */ -export const setStyle: ApplyStylingFn = - (renderer: Renderer3 | null, native: RElement, prop: string, value: string | null) => { - if (renderer !== null) { - // Use `isStylingValueDefined` to account for falsy values that should be bound like 0. - if (isStylingValueDefined(value)) { - // opacity, z-index and flexbox all have number values - // and these need to be converted into strings so that - // they can be assigned properly. - value = value.toString(); - ngDevMode && ngDevMode.rendererSetStyle++; - if (isProceduralRenderer(renderer)) { - renderer.setStyle(native, prop, value, RendererStyleFlags3.DashCase); - } else { - // The reason why native style may be `null` is either because - // it's a container element or it's a part of a test - // environment that doesn't have styling. In either - // case it's safe not to apply styling to the element. - const nativeStyle = native.style; - if (nativeStyle != null) { - nativeStyle.setProperty(prop, value); - } - } - } else { - ngDevMode && ngDevMode.rendererRemoveStyle++; - - if (isProceduralRenderer(renderer)) { - renderer.removeStyle(native, prop, RendererStyleFlags3.DashCase); - } else { - const nativeStyle = native.style; - if (nativeStyle != null) { - nativeStyle.removeProperty(prop); - } - } - } - } - }; - -/** - * Adds/removes the provided className value to the provided element. - */ -export const setClass: ApplyStylingFn = - (renderer: Renderer3 | null, native: RElement, className: string, value: any) => { - if (renderer !== null && className !== '') { - if (value) { - ngDevMode && ngDevMode.rendererAddClass++; - if (isProceduralRenderer(renderer)) { - renderer.addClass(native, className); - } else { - // the reason why classList may be `null` is either because - // it's a container element or it's a part of a test - // environment that doesn't have styling. In either - // case it's safe not to apply styling to the element. - const classList = native.classList; - if (classList != null) { - classList.add(className); - } - } - } else { - ngDevMode && ngDevMode.rendererRemoveClass++; - if (isProceduralRenderer(renderer)) { - renderer.removeClass(native, className); - } else { - const classList = native.classList; - if (classList != null) { - classList.remove(className); - } - } - } - } - }; - -export const setClassName = (renderer: Renderer3 | null, native: RElement, className: string) => { - if (renderer !== null) { - if (isProceduralRenderer(renderer)) { - renderer.setAttribute(native, 'class', className); - } else { - native.className = className; - } - } -}; - -export const setStyleAttr = (renderer: Renderer3 | null, native: RElement, value: string) => { - if (renderer !== null) { - if (isProceduralRenderer(renderer)) { - renderer.setAttribute(native, 'style', value); - } else { - native.setAttribute('style', value); - } - } -}; - -/** - * Iterates over all provided styling entries and renders them on the element. - * - * This function is used alongside a `StylingMapArray` entry. This entry is not - * the same as the `TStylingContext` and is only really used when an element contains - * initial styling values (e.g. `
`), but no style/class bindings - * are present. If and when that happens then this function will be called to render all - * initial styling values on an element. - */ -export function renderStylingMap( - renderer: Renderer3, element: RElement, - stylingValues: TStylingContext | StylingMapArray | string | null, isClassBased: boolean): void { - const stylingMapArr = getStylingMapArray(stylingValues); - if (stylingMapArr) { - for (let i = StylingMapArrayIndex.ValuesStartPosition; i < stylingMapArr.length; - i += StylingMapArrayIndex.TupleSize) { - const prop = getMapProp(stylingMapArr, i); - const value = getMapValue(stylingMapArr, i); - if (isClassBased) { - setClass(renderer, element, prop, value, null); - } else { - setStyle(renderer, element, prop, value, null); - } - } - } -} - -function objectToClassName(obj: {[key: string]: any} | null): string { - let str = ''; - if (obj) { - for (let key in obj) { - const value = obj[key]; - if (value) { - str += (str.length ? ' ' : '') + key; - } - } - } - return str; -} - -/** - * Determines whether or not an element style/className value has changed since the last update. - * - * This function helps Angular determine if a style or class attribute value was - * modified by an external plugin or API outside of the style binding code. This - * means any JS code that adds/removes class/style values on an element outside - * of Angular's styling binding algorithm. - * - * @returns true when the value was modified externally. - */ -function checkIfExternallyModified(element: HTMLElement, cachedValue: any, isClassBased: boolean) { - // this means it was checked before and there is no reason - // to compare the style/class values again. Either that or - // web workers are being used. - if (global.Node === 'undefined' || cachedValue === VALUE_IS_EXTERNALLY_MODIFIED) return true; - - // comparing the DOM value against the cached value is the best way to - // see if something has changed. - const currentValue = - (isClassBased ? element.className : (element.style && element.style.cssText)) || ''; - return currentValue !== (cachedValue || ''); -} - -/** - * Removes provided styling values from the element - */ -function removeStylingValues( - renderer: any, element: RElement, values: string | {[key: string]: any} | StylingMapArray, - isClassBased: boolean) { - let arr: StylingMapArray; - if (isStylingMapArray(values)) { - arr = values as StylingMapArray; - } else { - arr = normalizeIntoStylingMap(null, values, !isClassBased); - } - - const applyFn = isClassBased ? setClass : setStyle; - for (let i = StylingMapArrayIndex.ValuesStartPosition; i < arr.length; - i += StylingMapArrayIndex.TupleSize) { - const value = getMapValue(arr, i); - if (value) { - const prop = getMapProp(arr, i); - applyFn(renderer, element, prop, null); - } - } -} diff --git a/packages/core/src/render3/styling/map_based_bindings.ts b/packages/core/src/render3/styling/map_based_bindings.ts deleted file mode 100644 index 2b6974f833..0000000000 --- a/packages/core/src/render3/styling/map_based_bindings.ts +++ /dev/null @@ -1,362 +0,0 @@ -/** -* @license -* Copyright Google Inc. All Rights Reserved. -* -* Use of this source code is governed by an MIT-style license that can be -* found in the LICENSE file at https://angular.io/license -*/ -import {unwrapSafeValue} from '../../sanitization/bypass'; -import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanitizer'; -import {ProceduralRenderer3, RElement, Renderer3} from '../interfaces/renderer'; -import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingContext, TStylingContextIndex} from '../interfaces/styling'; -import {getBindingValue, getMapProp, getMapValue, getValue, getValuesCount, isStylingValueDefined} from '../util/styling_utils'; - -import {setStylingMapsSyncFn} from './bindings'; - - -/** - * -------- - * - * This file contains the algorithm logic for applying map-based bindings - * such as `[style]` and `[class]`. - * - * -------- - */ - -/** - * Enables support for map-based styling bindings (e.g. `[style]` and `[class]` bindings). - */ -export function activateStylingMapFeature() { - setStylingMapsSyncFn(syncStylingMap); -} - -/** - * Used to apply styling values presently within any map-based bindings on an element. - * - * Angular supports map-based styling bindings which can be applied via the - * `[style]` and `[class]` bindings which can be placed on any HTML element. - * These bindings can work independently, together or alongside prop-based - * styling bindings (e.g. `
`). - * - * If a map-based styling binding is detected by the compiler, the following - * AOT code is produced: - * - * ```typescript - * styleMap(ctx.styles); // styles = {key:value} - * classMap(ctx.classes); // classes = {key:value}|string - * ``` - * - * If and when either of the instructions above are evaluated, then the code - * present in this file is included into the bundle. The mechanism used, to - * activate support for map-based bindings at runtime is possible via the - * `activeStylingMapFeature` function (which is also present in this file). - * - * # The Algorithm - * Whenever a map-based binding updates (which is when the identity of the - * map-value changes) then the map is iterated over and a `StylingMapArray` array - * is produced. The `StylingMapArray` instance is stored in the binding location - * where the `BINDING_INDEX` is situated when the `styleMap()` or `classMap()` - * instruction were called. Once the binding changes, then the internal `bitMask` - * value is marked as dirty. - * - * Styling values are applied once CD exits the element (which happens when - * the `advance(n)` instruction is called or the template function exits). When - * this occurs, all prop-based bindings are applied. If a map-based binding is - * present then a special flushing function (called a sync function) is made - * available and it will be called each time a styling property is flushed. - * - * The flushing algorithm is designed to apply styling for a property (which is - * a CSS property or a className value) one by one. If map-based bindings - * are present, then the flushing algorithm will keep calling the maps styling - * sync function each time a property is visited. This way, the flushing - * behavior of map-based bindings will always be at the same property level - * as the current prop-based property being iterated over (because everything - * is alphabetically sorted). - * - * Let's imagine we have the following HTML template code: - * - * ```html - *
...
- * ``` - * - * When CD occurs, both the `[style]` and `[style.width]` bindings - * are evaluated. Then when the styles are flushed on screen, the - * following operations happen: - * - * 1. `[style.width]` is attempted to be written to the element. - * - * 2. Once that happens, the algorithm instructs the map-based - * entries (`[style]` in this case) to "catch up" and apply - * all values up to the `width` value. When this happens the - * `height` value is applied to the element (since it is - * alphabetically situated before the `width` property). - * - * 3. Since there are no more prop-based entries anymore, the - * loop exits and then, just before the flushing ends, it - * instructs all map-based bindings to "finish up" applying - * their values. - * - * 4. The only remaining value within the map-based entries is - * the `z-index` value (`width` got skipped because it was - * successfully applied via the prop-based `[style.width]` - * binding). Since all map-based entries are told to "finish up", - * the `z-index` value is iterated over and it is then applied - * to the element. - * - * The most important thing to take note of here is that prop-based - * bindings are evaluated in order alongside map-based bindings. - * This allows all styling across an element to be applied in O(n) - * time (a similar algorithm is that of the array merge algorithm - * in merge sort). - */ -export const syncStylingMap: SyncStylingMapsFn = - (context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, - data: LStylingData, sourceIndex: number, applyStylingFn: ApplyStylingFn, - sanitizer: StyleSanitizeFn | null, mode: StylingMapsSyncMode, targetProp?: string | null, - defaultValue?: string | boolean | null): boolean => { - let targetPropValueWasApplied = false; - - // once the map-based styling code is activate it is never deactivated. For this reason a - // check to see if the current styling context has any map based bindings is required. - const totalMaps = getValuesCount(context); - if (totalMaps) { - let runTheSyncAlgorithm = true; - const loopUntilEnd = !targetProp; - - // If the code is told to finish up (run until the end), but the mode - // hasn't been flagged to apply values (it only traverses values) then - // there is no point in iterating over the array because nothing will - // be applied to the element. - if (loopUntilEnd && (mode & StylingMapsSyncMode.ApplyAllValues) === 0) { - runTheSyncAlgorithm = false; - targetPropValueWasApplied = true; - } - - if (runTheSyncAlgorithm) { - targetPropValueWasApplied = innerSyncStylingMap( - context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp || null, - sourceIndex, defaultValue || null); - } - - if (loopUntilEnd) { - resetSyncCursors(); - } - } - - return targetPropValueWasApplied; - }; - -/** - * Recursive function designed to apply map-based styling to an element one map at a time. - * - * This function is designed to be called from the `syncStylingMap` function and will - * apply map-based styling data one map at a time to the provided `element`. - * - * This function is recursive and it will call itself if a follow-up map value is to be - * processed. To learn more about how the algorithm works, see `syncStylingMap`. - */ -function innerSyncStylingMap( - context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, - data: LStylingData, applyStylingFn: ApplyStylingFn, sanitizer: StyleSanitizeFn | null, - mode: StylingMapsSyncMode, targetProp: string | null, currentMapIndex: number, - defaultValue: string | boolean | null): boolean { - const totalMaps = getValuesCount(context) - 1; // maps have no default value - const mapsLimit = totalMaps - 1; - const recurseInnerMaps = - currentMapIndex < mapsLimit && (mode & StylingMapsSyncMode.RecurseInnerMaps) !== 0; - const checkValuesOnly = (mode & StylingMapsSyncMode.CheckValuesOnly) !== 0; - - if (checkValuesOnly) { - // inner modes do not check values ever (that can only happen - // when sourceIndex === 0) - mode &= ~StylingMapsSyncMode.CheckValuesOnly; - } - - let targetPropValueWasApplied = false; - if (currentMapIndex <= mapsLimit) { - let cursor = getCurrentSyncCursor(currentMapIndex); - const bindingIndex = getBindingValue( - context, TStylingContextIndex.ValuesStartPosition, currentMapIndex) as number; - const stylingMapArr = getValue(data, bindingIndex); - - if (stylingMapArr) { - while (cursor < stylingMapArr.length) { - const prop = getMapProp(stylingMapArr, cursor); - const iteratedTooFar = targetProp && prop > targetProp; - const isTargetPropMatched = !iteratedTooFar && prop === targetProp; - const value = getMapValue(stylingMapArr, cursor); - const valueIsDefined = isStylingValueDefined(value); - - // the recursive code is designed to keep applying until - // it reaches or goes past the target prop. If and when - // this happens then it will stop processing values, but - // all other map values must also catch up to the same - // point. This is why a recursive call is still issued - // even if the code has iterated too far. - const innerMode = - iteratedTooFar ? mode : resolveInnerMapMode(mode, valueIsDefined, isTargetPropMatched); - - const innerProp = iteratedTooFar ? targetProp : prop; - let valueApplied = recurseInnerMaps ? - innerSyncStylingMap( - context, renderer, element, data, applyStylingFn, sanitizer, innerMode, innerProp, - currentMapIndex + 1, defaultValue) : - false; - - if (iteratedTooFar) { - if (!targetPropValueWasApplied) { - targetPropValueWasApplied = valueApplied; - } - break; - } - - if (!valueApplied && isValueAllowedToBeApplied(mode, isTargetPropMatched)) { - valueApplied = true; - - if (!checkValuesOnly) { - const useDefault = isTargetPropMatched && !valueIsDefined; - const bindingIndexToApply = isTargetPropMatched ? bindingIndex : null; - - let finalValue: any; - if (useDefault) { - finalValue = defaultValue; - } else { - finalValue = sanitizer ? - sanitizer(prop, value, StyleSanitizeMode.ValidateAndSanitize) : - (value ? unwrapSafeValue(value) : null); - } - - applyStylingFn(renderer, element, prop, finalValue, bindingIndexToApply); - } - } - - targetPropValueWasApplied = valueApplied && isTargetPropMatched; - cursor += StylingMapArrayIndex.TupleSize; - } - setCurrentSyncCursor(currentMapIndex, cursor); - - // this is a fallback case in the event that the styling map is `null` for this - // binding but there are other map-based bindings that need to be evaluated - // afterwards. If the `prop` value is falsy then the intention is to cycle - // through all of the properties in the remaining maps as well. If the current - // styling map is too short then there are no values to iterate over. In either - // case the follow-up maps need to be iterated over. - if (recurseInnerMaps && - (stylingMapArr.length === StylingMapArrayIndex.ValuesStartPosition || !targetProp)) { - targetPropValueWasApplied = innerSyncStylingMap( - context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp, - currentMapIndex + 1, defaultValue); - } - } else if (recurseInnerMaps) { - targetPropValueWasApplied = innerSyncStylingMap( - context, renderer, element, data, applyStylingFn, sanitizer, mode, targetProp, - currentMapIndex + 1, defaultValue); - } - } - - return targetPropValueWasApplied; -} - -/** - * Used to determine the mode for the inner recursive call. - * - * If an inner map is iterated on then this is done so for one - * of two reasons: - * - * - value is being applied: - * if the value is being applied from this current styling - * map then there is no need to apply it in a deeper map - * (i.e. the `SkipTargetProp` flag is set) - * - * - value is being not applied: - * apply the value if it is found in a deeper map. - * (i.e. the `SkipTargetProp` flag is unset) - * - * When these reasons are encountered the flags will for the - * inner map mode will be configured. - */ -function resolveInnerMapMode( - currentMode: number, valueIsDefined: boolean, isTargetPropMatched: boolean): number { - let innerMode = currentMode; - - // the statements below figures out whether or not an inner styling map - // is allowed to apply its value or not. The main thing to keep note - // of is that if the target prop isn't matched then its expected that - // all values before it are allowed to be applied so long as "apply all values" - // is set to true. - const applyAllValues = currentMode & StylingMapsSyncMode.ApplyAllValues; - const applyTargetProp = currentMode & StylingMapsSyncMode.ApplyTargetProp; - const allowInnerApply = - !valueIsDefined && (isTargetPropMatched ? applyTargetProp : applyAllValues); - - if (allowInnerApply) { - // case 1: set the mode to apply the targeted prop value if it - // ends up being encountered in another map value - innerMode |= StylingMapsSyncMode.ApplyTargetProp; - innerMode &= ~StylingMapsSyncMode.SkipTargetProp; - } else { - // case 2: set the mode to skip the targeted prop value if it - // ends up being encountered in another map value - innerMode |= StylingMapsSyncMode.SkipTargetProp; - innerMode &= ~StylingMapsSyncMode.ApplyTargetProp; - } - - return innerMode; -} - -/** - * Decides whether or not a prop/value entry will be applied to an element. - * - * To determine whether or not a value is to be applied, - * the following procedure is evaluated: - * - * First check to see the current `mode` status: - * 1. If the mode value permits all props to be applied then allow. - * - But do not allow if the current prop is set to be skipped. - * 2. Otherwise if the current prop is permitted then allow. - */ -function isValueAllowedToBeApplied(mode: StylingMapsSyncMode, isTargetPropMatched: boolean) { - let doApplyValue = (mode & StylingMapsSyncMode.ApplyAllValues) !== 0; - if (!doApplyValue) { - if (mode & StylingMapsSyncMode.ApplyTargetProp) { - doApplyValue = isTargetPropMatched; - } - } else if ((mode & StylingMapsSyncMode.SkipTargetProp) && isTargetPropMatched) { - doApplyValue = false; - } - return doApplyValue; -} - -/** - * Used to keep track of concurrent cursor values for multiple map-based styling bindings present on - * an element. - */ -const MAP_CURSORS: number[] = []; - -/** - * Used to reset the state of each cursor value being used to iterate over map-based styling - * bindings. - */ -function resetSyncCursors() { - for (let i = 0; i < MAP_CURSORS.length; i++) { - MAP_CURSORS[i] = StylingMapArrayIndex.ValuesStartPosition; - } -} - -/** - * Returns an active cursor value at a given mapIndex location. - */ -function getCurrentSyncCursor(mapIndex: number) { - if (mapIndex >= MAP_CURSORS.length) { - MAP_CURSORS.push(StylingMapArrayIndex.ValuesStartPosition); - } - return MAP_CURSORS[mapIndex]; -} - -/** - * Sets a cursor value at a given mapIndex location. - */ -function setCurrentSyncCursor(mapIndex: number, indexValue: number) { - MAP_CURSORS[mapIndex] = indexValue; -} diff --git a/packages/core/src/render3/styling/state.ts b/packages/core/src/render3/styling/state.ts deleted file mode 100644 index 73f5fc42e1..0000000000 --- a/packages/core/src/render3/styling/state.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** -* @license -* Copyright Google Inc. All Rights Reserved. -* -* Use of this source code is governed by an MIT-style license that can be -* found in the LICENSE file at https://angular.io/license -*/ -import {RElement} from '../interfaces/renderer'; -import {StylingMapArray} from '../interfaces/styling'; -import {TEMPLATE_DIRECTIVE_INDEX} from '../util/styling_utils'; - -/** - * -------- - * - * This file contains all state-based logic for styling in Angular. - * - * Styling in Angular is evaluated with a series of styling-specific - * template instructions which are called one after another each time - * change detection occurs in Angular. - * - * Styling makes use of various temporary, state-based variables between - * instructions so that it can better cache and optimize its values. - * These values are usually populated and cleared when an element is - * exited in change detection (once all the instructions are run for - * that element). - * - * To learn more about the algorithm see `TStylingContext`. - * - * -------- - */ - -/** - * Used as a state reference for update values between style/class binding instructions. - * - * In addition to storing the element and bit-mask related values, the state also - * stores the `sourceIndex` value. The `sourceIndex` value is an incremented value - * that identifies what "source" (i.e. the template, a specific directive by index or - * component) is currently applying its styling bindings to the element. - */ -export interface StylingState { - /** The element that is currently being processed */ - element: RElement|null; - - /** The directive index that is currently active (`0` === template) */ - directiveIndex: number; - - /** The source (column) index that is currently active (`0` === template) */ - sourceIndex: number; - - /** The classes update bit mask value that is processed during each class binding */ - classesBitMask: number; - - /** The classes update bit index value that is processed during each class binding */ - classesIndex: number; - - /** The styles update bit mask value that is processed during each style binding */ - stylesBitMask: number; - - /** The styles update bit index value that is processed during each style binding */ - stylesIndex: number; - - /** - * The last class map that was applied (i.e. `[class]="x"`). - * - * Note that this property is only populated when direct class values are applied - * (i.e. context resolution is not used). - * - * See `allowDirectStyling` for more info. - */ - lastDirectClassMap: StylingMapArray|null; - - /** - * The last style map that was applied (i.e. `[style]="x"`) - * - * Note that this property is only populated when direct style values are applied - * (i.e. context resolution is not used). - * - * See `allowDirectStyling` for more info. - */ - lastDirectStyleMap: StylingMapArray|null; -} - -// these values will get filled in the very first time this is accessed... -const _state: StylingState = { - element: null, - directiveIndex: -1, - sourceIndex: -1, - classesBitMask: -1, - classesIndex: -1, - stylesBitMask: -1, - stylesIndex: -1, - lastDirectClassMap: null, - lastDirectStyleMap: null, -}; - -const BIT_MASK_START_VALUE = 0; - -// the `0` start value is reserved for [map]-based entries -const INDEX_START_VALUE = 1; - -/** - * Returns (or instantiates) the styling state for the given element. - * - * Styling state is accessed and processed each time a style or class binding - * is evaluated. - * - * If and when the provided `element` doesn't match the current element in the - * state then this means that styling was recently cleared or the element has - * changed in change detection. In both cases the styling state is fully reset. - * - * If and when the provided `directiveIndex` doesn't match the current directive - * index in the state then this means that a new source has introduced itself into - * the styling code (or, in other words, another directive or component has started - * to apply its styling host bindings to the element). - */ -export function getStylingState(element: RElement, directiveIndex: number): StylingState { - if (_state.element !== element) { - _state.element = element; - _state.directiveIndex = directiveIndex; - _state.sourceIndex = directiveIndex === TEMPLATE_DIRECTIVE_INDEX ? 0 : 1; - _state.classesBitMask = BIT_MASK_START_VALUE; - _state.classesIndex = INDEX_START_VALUE; - _state.stylesBitMask = BIT_MASK_START_VALUE; - _state.stylesIndex = INDEX_START_VALUE; - _state.lastDirectClassMap = null; - _state.lastDirectStyleMap = null; - } else if (_state.directiveIndex !== directiveIndex) { - _state.directiveIndex = directiveIndex; - _state.sourceIndex++; - } - return _state; -} - -/** - * Clears the styling state so that it can be used by another element's styling code. - */ -export function resetStylingState() { - _state.element = null; -} diff --git a/packages/core/src/render3/styling/styling_debug.ts b/packages/core/src/render3/styling/styling_debug.ts index d901b91659..937c09aadb 100644 --- a/packages/core/src/render3/styling/styling_debug.ts +++ b/packages/core/src/render3/styling/styling_debug.ts @@ -12,10 +12,6 @@ import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex, TSt import {TData} from '../interfaces/view'; import {getCurrentStyleSanitizer} from '../state'; import {attachDebugObject} from '../util/debug_utils'; -import {MAP_BASED_ENTRY_PROP_NAME, TEMPLATE_DIRECTIVE_INDEX, allowDirectStyling as _allowDirectStyling, getBindingValue, getDefaultValue, getGuardMask, getProp, getPropValuesStartPosition, getValue, getValuesCount, hasConfig, isSanitizationRequired, isStylingContext, normalizeIntoStylingMap, setValue} from '../util/styling_utils'; - -import {applyStylingViaContext} from './bindings'; -import {activateStylingMapFeature} from './map_based_bindings'; @@ -148,356 +144,6 @@ export interface DebugNodeStylingEntry { } -/** - * Instantiates and attaches an instance of `TStylingContextDebug` to the provided context - */ -export function attachStylingDebugObject( - context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) { - const debug = new TStylingContextDebug(context, tNode, isClassBased); - attachDebugObject(context, debug); - return debug; -} - -/** - * A human-readable debug summary of the styling data present within `TStylingContext`. - * - * This class is designed to be used within testing code or when an - * application has `ngDevMode` activated. - */ -class TStylingContextDebug implements DebugStylingContext { - constructor( - public readonly context: TStylingContext, private _tNode: TStylingNode, - private _isClassBased: boolean) {} - - get config(): DebugStylingConfig { return buildConfig(this._tNode, this._isClassBased); } - - /** - * Returns a detailed summary of each styling entry in the context. - * - * See `DebugStylingContextEntry`. - */ - get entries(): {[prop: string]: DebugStylingContextEntry} { - const context = this.context; - const totalColumns = getValuesCount(context); - const entries: {[prop: string]: DebugStylingContextEntry} = {}; - const start = getPropValuesStartPosition(context, this._tNode, this._isClassBased); - let i = start; - while (i < context.length) { - const prop = getProp(context, i); - const templateBitMask = getGuardMask(context, i, false); - const hostBindingsBitMask = getGuardMask(context, i, true); - const defaultValue = getDefaultValue(context, i); - const sanitizationRequired = isSanitizationRequired(context, i); - const bindingsStartPosition = i + TStylingContextIndex.BindingsStartOffset; - - const sources: (number | string | null)[] = []; - - for (let j = 0; j < totalColumns; j++) { - const bindingIndex = context[bindingsStartPosition + j] as number | string | null; - if (bindingIndex !== 0) { - sources.push(bindingIndex); - } - } - - entries[prop] = { - prop, - templateBitMask, - hostBindingsBitMask, - sanitizationRequired, - valuesCount: sources.length, defaultValue, sources, - }; - - i += TStylingContextIndex.BindingsStartOffset + totalColumns; - } - return entries; - } - - /** - * Prints a detailed summary of each styling source grouped together with each binding index in - * the context. - */ - printSources(): void { - let output = '\n'; - - const context = this.context; - const prefix = this._isClassBased ? 'class' : 'style'; - const bindingsBySource: { - type: string, - entries: {binding: string, bindingIndex: number, value: any, bitMask: number}[] - }[] = []; - - const totalColumns = getValuesCount(context); - const itemsPerRow = TStylingContextIndex.BindingsStartOffset + totalColumns; - - for (let i = 0; i < totalColumns; i++) { - const isDefaultColumn = i === totalColumns - 1; - const hostBindingsMode = i !== TEMPLATE_DIRECTIVE_INDEX; - const type = getTypeFromColumn(i, totalColumns); - const entries: {binding: string, value: any, bindingIndex: number, bitMask: number}[] = []; - - let j = TStylingContextIndex.ValuesStartPosition; - while (j < context.length) { - const value = getBindingValue(context, j, i); - if (isDefaultColumn || value > 0) { - const bitMask = getGuardMask(context, j, hostBindingsMode); - const bindingIndex = isDefaultColumn ? -1 : value as number; - const prop = getProp(context, j); - const isMapBased = prop === MAP_BASED_ENTRY_PROP_NAME; - const binding = `${prefix}${isMapBased ? '' : '.' + prop}`; - entries.push({binding, value, bindingIndex, bitMask}); - } - j += itemsPerRow; - } - - bindingsBySource.push( - {type, entries: entries.sort((a, b) => a.bindingIndex - b.bindingIndex)}); - } - - bindingsBySource.forEach(entry => { - output += `[${entry.type.toUpperCase()}]\n`; - output += repeat('-', entry.type.length + 2) + '\n'; - - let tab = ' '; - entry.entries.forEach(entry => { - const isDefault = typeof entry.value !== 'number'; - const value = entry.value; - if (!isDefault || value !== null) { - output += `${tab}[${entry.binding}] = \`${value}\``; - output += '\n'; - } - }); - output += '\n'; - }); - - /* tslint:disable */ - console.log(output); - } - - /** - * Prints a detailed table of the entire styling context. - */ - printTable(): void { - // IE (not Edge) is the only browser that doesn't support this feature. Because - // these debugging tools are not apart of the core of Angular (they are just - // extra tools) we can skip-out on older browsers. - if (!console.table) { - throw new Error('This feature is not supported in your browser'); - } - - const context = this.context; - const table: any[] = []; - const totalColumns = getValuesCount(context); - const itemsPerRow = TStylingContextIndex.BindingsStartOffset + totalColumns; - const totalProps = Math.floor(context.length / itemsPerRow); - - let i = TStylingContextIndex.ValuesStartPosition; - while (i < context.length) { - const prop = getProp(context, i); - const isMapBased = prop === MAP_BASED_ENTRY_PROP_NAME; - const entry: {[key: string]: any} = { - prop, - 'tpl mask': generateBitString(getGuardMask(context, i, false), isMapBased, totalProps), - 'host mask': generateBitString(getGuardMask(context, i, true), isMapBased, totalProps), - }; - - for (let j = 0; j < totalColumns; j++) { - const key = getTypeFromColumn(j, totalColumns); - const value = getBindingValue(context, i, j); - entry[key] = value; - } - - i += itemsPerRow; - table.push(entry); - } - - /* tslint:disable */ - console.table(table); - } -} - -function generateBitString(value: number, isMapBased: boolean, totalProps: number) { - if (isMapBased || value > 1) { - return `0b${leftPad(value.toString(2), totalProps, '0')}`; - } - return null; -} - -function leftPad(value: string, max: number, pad: string) { - return repeat(pad, max - value.length) + value; -} - -function getTypeFromColumn(index: number, totalColumns: number) { - if (index === TEMPLATE_DIRECTIVE_INDEX) { - return 'template'; - } else if (index === totalColumns - 1) { - return 'defaults'; - } else { - return `dir #${index}`; - } -} - -function repeat(c: string, times: number) { - let s = ''; - for (let i = 0; i < times; i++) { - s += c; - } - return s; -} - -/** - * A human-readable debug summary of the styling data present for a `DebugNode` instance. - * - * This class is designed to be used within testing code or when an - * application has `ngDevMode` activated. - */ -export class NodeStylingDebug implements DebugNodeStyling { - private _sanitizer: StyleSanitizeFn|null = null; - private _debugContext: DebugStylingContext; - - constructor( - context: TStylingContext|DebugStylingContext, private _tNode: TStylingNode, - private _data: LStylingData, private _isClassBased: boolean) { - this._debugContext = isStylingContext(context) ? - new TStylingContextDebug(context as TStylingContext, _tNode, _isClassBased) : - (context as DebugStylingContext); - } - - get context() { return this._debugContext; } - - /** - * Overrides the sanitizer used to process styles. - */ - overrideSanitizer(sanitizer: StyleSanitizeFn|null) { this._sanitizer = sanitizer; } - - /** - * Returns a detailed summary of each styling entry in the context and - * what their runtime representation is. - * - * See `LStylingSummary`. - */ - get summary(): {[key: string]: DebugNodeStylingEntry} { - const entries: {[key: string]: DebugNodeStylingEntry} = {}; - const config = this.config; - - let data = this._data; - - // the direct pass code doesn't convert [style] or [class] values - // into StylingMapArray instances. For this reason, the values - // need to be converted ahead of time since the styling debug - // relies on context resolution to figure out what styling - // values have been added/removed on the element. - if (config.allowDirectStyling && config.hasMapBindings) { - data = data.concat([]); // make a copy - this._convertMapBindingsToStylingMapArrays(data); - } - - this._mapValues(data, (prop: string, value: any, bindingIndex: number | null) => { - entries[prop] = {prop, value, bindingIndex}; - }); - - return entries; - } - - get config() { return buildConfig(this._tNode, this._isClassBased); } - - /** - * Returns a key/value map of all the styles/classes that were last applied to the element. - */ - get values(): {[key: string]: any} { - const entries: {[key: string]: any} = {}; - const config = this.config; - let data = this._data; - - // the direct pass code doesn't convert [style] or [class] values - // into StylingMapArray instances. For this reason, the values - // need to be converted ahead of time since the styling debug - // relies on context resolution to figure out what styling - // values have been added/removed on the element. - if (config.allowDirectStyling && config.hasMapBindings) { - data = data.concat([]); // make a copy - this._convertMapBindingsToStylingMapArrays(data); - } - - this._mapValues(data, (prop: string, value: any) => { entries[prop] = value; }); - return entries; - } - - private _convertMapBindingsToStylingMapArrays(data: LStylingData) { - const context = this.context.context; - const limit = getPropValuesStartPosition(context, this._tNode, this._isClassBased); - for (let i = - TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset; - i < limit; i++) { - const bindingIndex = context[i] as number; - const bindingValue = bindingIndex !== 0 ? getValue(data, bindingIndex) : null; - if (bindingValue && !Array.isArray(bindingValue)) { - const stylingMapArray = normalizeIntoStylingMap(null, bindingValue, !this._isClassBased); - setValue(data, bindingIndex, stylingMapArray); - } - } - } - - private _mapValues( - data: LStylingData, - fn: (prop: string, value: string|null, bindingIndex: number|null) => any) { - // there is no need to store/track an element instance. The - // element is only used when the styling algorithm attempts to - // style the value (and we mock out the stylingApplyFn anyway). - const mockElement = {} as any; - const mapBindingsFlag = - this._isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; - const hasMaps = hasConfig(this._tNode, mapBindingsFlag); - if (hasMaps) { - activateStylingMapFeature(); - } - - const mapFn: ApplyStylingFn = - (renderer: any, element: RElement, prop: string, value: string | null, - bindingIndex?: number | null) => fn(prop, value, bindingIndex || null); - - const sanitizer = this._isClassBased ? null : (this._sanitizer || getCurrentStyleSanitizer()); - - // run the template bindings - applyStylingViaContext( - this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, false, - this._isClassBased); - - // and also the host bindings - applyStylingViaContext( - this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, true, - this._isClassBased); - } -} - -function buildConfig(tNode: TStylingNode, isClassBased: boolean): DebugStylingConfig { - const hasMapBindings = hasConfig( - tNode, isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings); - const hasPropBindings = hasConfig( - tNode, isClassBased ? TNodeFlags.hasClassPropBindings : TNodeFlags.hasStylePropBindings); - const hasCollisions = hasConfig( - tNode, - isClassBased ? TNodeFlags.hasDuplicateClassBindings : TNodeFlags.hasDuplicateStyleBindings); - const hasTemplateBindings = hasConfig( - tNode, - isClassBased ? TNodeFlags.hasTemplateClassBindings : TNodeFlags.hasTemplateStyleBindings); - const hasHostBindings = hasConfig( - tNode, isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings); - - // `firstTemplatePass` here is false because the context has already been constructed - // directly within the behavior of the debugging tools (outside of style/class debugging, - // the context is constructed during the first template pass). - const allowDirectStyling = _allowDirectStyling(tNode, isClassBased, false); - return { - hasMapBindings, // - hasPropBindings, // - hasCollisions, // - hasTemplateBindings, // - hasHostBindings, // - allowDirectStyling, // - }; -} - - /** * Find the head of the styling binding linked list. */ diff --git a/packages/core/src/render3/util/styling_utils.ts b/packages/core/src/render3/util/styling_utils.ts deleted file mode 100644 index a89a69c93e..0000000000 --- a/packages/core/src/render3/util/styling_utils.ts +++ /dev/null @@ -1,463 +0,0 @@ -/** -* @license -* Copyright Google Inc. All Rights Reserved. -* -* Use of this source code is governed by an MIT-style license that can be -* found in the LICENSE file at https://angular.io/license -*/ -import {unwrapSafeValue} from '../../sanitization/bypass'; -import {CharCode} from '../../util/char_code'; -import {PropertyAliases, TNodeFlags} from '../interfaces/node'; -import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags, TStylingNode} from '../interfaces/styling'; -import {NO_CHANGE} from '../tokens'; - -export const MAP_BASED_ENTRY_PROP_NAME = '[MAP]'; -export const TEMPLATE_DIRECTIVE_INDEX = 0; - -/** - * Default fallback value for a styling binding. - * - * A value of `null` is used here which signals to the styling algorithm that - * the styling value is not present. This way if there are no other values - * detected then it will be removed once the style/class property is dirty and - * diffed within the styling algorithm present in `flushStyling`. - */ -export const DEFAULT_BINDING_VALUE = null; - -export const DEFAULT_BINDING_INDEX = 0; - -const DEFAULT_TOTAL_SOURCES = 1; - -// The first bit value reflects a map-based binding value's bit. -// The reason why it's always activated for every entry in the map -// is so that if any map-binding values update then all other prop -// based bindings will pass the guard check automatically without -// any extra code or flags. -export const DEFAULT_GUARD_MASK_VALUE = 0b1; - -/** - * Creates a new instance of the `TStylingContext`. - * - * The `TStylingContext` is used as a manifest of all style or all class bindings on - * an element. Because it is a T-level data-structure, it is only created once per - * tNode for styles and for classes. This function allocates a new instance of a - * `TStylingContext` with the initial values (see `interfaces.ts` for more info). - */ -export function allocTStylingContext( - initialStyling: StylingMapArray | null, hasDirectives: boolean): TStylingContext { - initialStyling = initialStyling || allocStylingMapArray(null); - return [ - DEFAULT_TOTAL_SOURCES, // 1) total amount of styling sources (template, directives, etc...) - initialStyling, // 2) initial styling values - ]; -} - -export function allocStylingMapArray(value: {} | string | null): StylingMapArray { - return [value]; -} - -export function hasConfig(tNode: TStylingNode, flag: TNodeFlags) { - return (tNode.flags & flag) !== 0; -} - -/** - * Determines whether or not to apply styles/classes directly or via context resolution. - * - * There are three cases that are matched here: - * 1. there are no directives present AND `ngDevMode` is falsy - * 2. the `firstUpdatePass` has not already run (which means that - * there are more bindings to register and, therefore, direct - * style/class application is not yet possible) - * 3. There are no collisions (i.e. properties with more than one binding) across multiple - * sources (i.e. template + directive, directive + directive, directive + component) - */ -export function allowDirectStyling( - tNode: TStylingNode, isClassBased: boolean, firstUpdatePass: boolean): boolean { - let allow = false; - - // if no directives are present then we do not need populate a context at all. This - // is because duplicate prop bindings cannot be registered through the template. If - // and when this happens we can safely apply the value directly without context - // resolution... - const hasDirectives = hasConfig(tNode, TNodeFlags.hasHostBindings); - if (!hasDirectives) { - // `ngDevMode` is required to be checked here because tests/debugging rely on the context being - // populated. If things are in production mode then there is no need to build a context - // therefore the direct apply can be allowed (even on the first update). - allow = ngDevMode ? !firstUpdatePass : true; - } else if (!firstUpdatePass) { - const duplicateStylingFlag = - isClassBased ? TNodeFlags.hasDuplicateClassBindings : TNodeFlags.hasDuplicateStyleBindings; - const hasDuplicates = hasConfig(tNode, duplicateStylingFlag); - const hasOnlyMapOrPropsFlag = isClassBased ? TNodeFlags.hasClassPropAndMapBindings : - TNodeFlags.hasStylePropAndMapBindings; - const hasOnlyMapsOrOnlyProps = (tNode.flags & hasOnlyMapOrPropsFlag) !== hasOnlyMapOrPropsFlag; - allow = !hasDuplicates && hasOnlyMapsOrOnlyProps; - } - - return allow; -} - -export function patchConfig(tNode: TStylingNode, flag: TNodeFlags): void { - tNode.flags |= flag; -} - -export function getProp(context: TStylingContext, index: number): string { - return context[index + TStylingContextIndex.PropOffset] as string; -} - -function getPropConfig(context: TStylingContext, index: number): number { - return (context[index + TStylingContextIndex.ConfigOffset] as number) & - TStylingContextPropConfigFlags.Mask; -} - -export function isSanitizationRequired(context: TStylingContext, index: number): boolean { - return (getPropConfig(context, index) & TStylingContextPropConfigFlags.SanitizationRequired) !== - 0; -} - -export function getGuardMask( - context: TStylingContext, index: number, isHostBinding: boolean): number { - const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset : - TStylingContextIndex.TemplateBitGuardOffset); - return context[position] as number; -} - -export function setGuardMask( - context: TStylingContext, index: number, maskValue: number, isHostBinding: boolean) { - const position = index + (isHostBinding ? TStylingContextIndex.HostBindingsBitGuardOffset : - TStylingContextIndex.TemplateBitGuardOffset); - context[position] = maskValue; -} - -export function getValuesCount(context: TStylingContext): number { - return getTotalSources(context) + 1; -} - -export function getTotalSources(context: TStylingContext): number { - return context[TStylingContextIndex.TotalSourcesPosition]; -} - -export function getBindingValue(context: TStylingContext, index: number, offset: number) { - return context[index + TStylingContextIndex.BindingsStartOffset + offset] as number | string; -} - -export function getDefaultValue(context: TStylingContext, index: number): string|boolean|null { - return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] as - string | - boolean | null; -} - -export function setDefaultValue( - context: TStylingContext, index: number, value: string | boolean | null) { - return context[index + TStylingContextIndex.BindingsStartOffset + getTotalSources(context)] = - value; -} - -export function setValue(data: LStylingData, bindingIndex: number, value: any) { - data[bindingIndex] = value; -} - -export function getValue(data: LStylingData, bindingIndex: number): T|null { - return bindingIndex !== 0 ? data[bindingIndex] as T : null; -} - -export function getPropValuesStartPosition( - context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) { - let startPosition = TStylingContextIndex.ValuesStartPosition; - const flag = isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; - if (hasConfig(tNode, flag)) { - startPosition += TStylingContextIndex.BindingsStartOffset + getValuesCount(context); - } - return startPosition; -} - -export function hasValueChangedUnwrapSafeValue( - a: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined | {}, - b: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined | - {}): boolean { - return hasValueChanged(unwrapSafeValue(a), unwrapSafeValue(b)); -} - - -export function hasValueChanged( - a: NO_CHANGE | StylingMapArray | number | string | null | boolean | undefined | {}, - b: NO_CHANGE | StylingMapArray | number | string | null | boolean | undefined | {}): boolean { - if (b === NO_CHANGE) return false; - - const compareValueA = Array.isArray(a) ? a[StylingMapArrayIndex.RawValuePosition] : a; - const compareValueB = Array.isArray(b) ? b[StylingMapArrayIndex.RawValuePosition] : b; - return !Object.is(compareValueA, compareValueB); -} - -/** - * Determines whether the provided styling value is truthy or falsy. - */ -export function isStylingValueDefined(value: T): - value is NonNullable { - // the reason why null is compared against is because - // a CSS class value that is set to `false` must be - // respected (otherwise it would be treated as falsy). - // Empty string values are because developers usually - // set a value to an empty string to remove it. - return value != null && value !== ''; -} - -export function concatString(a: string, b: string, separator = ' '): string { - return a + ((b.length && a.length) ? separator : '') + b; -} - -export function hyphenate(value: string): string { - return value.replace(/[a-z][A-Z]/g, v => v.charAt(0) + '-' + v.charAt(1)).toLowerCase(); -} - -/** - * Returns an instance of `StylingMapArray`. - * - * This function is designed to find an instance of `StylingMapArray` in case it is stored - * inside of an instance of `TStylingContext`. When a styling context is created it - * will copy over an initial styling values from the tNode (which are stored as a - * `StylingMapArray` on the `tNode.classes` or `tNode.styles` values). - */ -export function getStylingMapArray(value: TStylingContext | StylingMapArray | string | null): - StylingMapArray|null { - // TODO(misko): remove after TNode.classes/styles becomes `string` only - if (typeof value === 'string') return null; - return isStylingContext(value) ? - (value as TStylingContext)[TStylingContextIndex.InitialStylingValuePosition] : - value as StylingMapArray; -} - -export function isStylingContext(value: any): boolean { - // the StylingMapArray is in the format of [initial, prop, string, prop, string] - // and this is the defining value to distinguish between arrays - return Array.isArray(value) && value.length >= TStylingContextIndex.ValuesStartPosition && - typeof value[1] !== 'string'; -} - -export function isStylingMapArray(value: any): boolean { - // the StylingMapArray is in the format of [initial, prop, string, prop, string] - // and this is the defining value to distinguish between arrays - return Array.isArray(value) && - (typeof(value as StylingMapArray)[StylingMapArrayIndex.ValuesStartPosition] === 'string'); -} - -export function getInitialStylingValue(context: TStylingContext | StylingMapArray | string | null): - string { - // TODO(misko): remove after TNode.classes/styles becomes `string` only - if (typeof context === 'string') return context; - const map = getStylingMapArray(context); - return map && (map[StylingMapArrayIndex.RawValuePosition] as string | null) || ''; -} - -export function hasClassInput(tNode: TStylingNode) { - return (tNode.flags & TNodeFlags.hasClassInput) !== 0; -} - -export function hasStyleInput(tNode: TStylingNode) { - return (tNode.flags & TNodeFlags.hasStyleInput) !== 0; -} - -export function getMapProp(map: StylingMapArray, index: number): string { - return map[index + StylingMapArrayIndex.PropOffset] as string; -} - -const MAP_DIRTY_VALUE = - typeof ngDevMode !== 'undefined' && ngDevMode ? {} : {MAP_DIRTY_VALUE: true}; - -export function setMapAsDirty(map: StylingMapArray): void { - map[StylingMapArrayIndex.RawValuePosition] = MAP_DIRTY_VALUE; -} - -export function setMapValue( - map: StylingMapArray, index: number, value: string | boolean | null): void { - map[index + StylingMapArrayIndex.ValueOffset] = value; -} - -export function getMapValue(map: StylingMapArray, index: number): string|null { - return map[index + StylingMapArrayIndex.ValueOffset] as string | null; -} - -export function forceClassesAsString(classes: string | {[key: string]: any} | null | undefined): - string { - if (classes && typeof classes !== 'string') { - classes = Object.keys(classes).join(' '); - } - return (classes as string) || ''; -} - -export function forceStylesAsString( - styles: {[key: string]: any} | string | null | undefined, hyphenateProps: boolean): string { - if (typeof styles == 'string') return styles; - let str = ''; - if (styles) { - const props = Object.keys(styles); - for (let i = 0; i < props.length; i++) { - const prop = props[i]; - const propLabel = hyphenateProps ? hyphenate(prop) : prop; - const value = styles[prop]; - if (value !== null) { - str = concatString(str, `${propLabel}:${value}`, ';'); - } - } - } - return str; -} - -export function isHostStylingActive(directiveOrSourceId: number): boolean { - return directiveOrSourceId !== TEMPLATE_DIRECTIVE_INDEX; -} - -/** - * Converts the provided styling map array into a string. - * - * Classes => `one two three` - * Styles => `prop:value; prop2:value2` - */ -export function stylingMapToString(map: StylingMapArray, isClassBased: boolean): string { - let str = ''; - for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length; - i += StylingMapArrayIndex.TupleSize) { - const prop = getMapProp(map, i); - const value = getMapValue(map, i) as string; - const attrValue = concatString(prop, isClassBased ? '' : value, ':'); - str = concatString(str, attrValue, isClassBased ? ' ' : '; '); - } - return str; -} - -/** - * Converts the provided styling map array into a key value map. - */ -export function stylingMapToStringMap(map: StylingMapArray | null): {[key: string]: any} { - let stringMap: {[key: string]: any} = {}; - if (map) { - for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length; - i += StylingMapArrayIndex.TupleSize) { - const prop = getMapProp(map, i); - const value = getMapValue(map, i) as string; - stringMap[prop] = value; - } - } - return stringMap; -} - -/** - * Inserts the provided item into the provided styling array at the right spot. - * - * The `StylingMapArray` type is a sorted key/value array of entries. This means - * that when a new entry is inserted it must be placed at the right spot in the - * array. This function figures out exactly where to place it. - */ -export function addItemToStylingMap( - stylingMapArr: StylingMapArray, prop: string, value: string | boolean | null, - allowOverwrite?: boolean) { - for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length; - j += StylingMapArrayIndex.TupleSize) { - const propAtIndex = getMapProp(stylingMapArr, j); - if (prop <= propAtIndex) { - let applied = false; - if (propAtIndex === prop) { - const valueAtIndex = stylingMapArr[j]; - if (allowOverwrite || !isStylingValueDefined(valueAtIndex)) { - applied = true; - setMapValue(stylingMapArr, j, value); - } - } else { - applied = true; - stylingMapArr.splice(j, 0, prop, value); - } - return applied; - } - } - - stylingMapArr.push(prop, value); - return true; -} - -/** - * Used to convert a {key:value} map into a `StylingMapArray` array. - * - * This function will either generate a new `StylingMapArray` instance - * or it will patch the provided `newValues` map value into an - * existing `StylingMapArray` value (this only happens if `bindingValue` - * is an instance of `StylingMapArray`). - * - * If a new key/value map is provided with an old `StylingMapArray` - * value then all properties will be overwritten with their new - * values or with `null`. This means that the array will never - * shrink in size (but it will also not be created and thrown - * away whenever the `{key:value}` map entries change). - */ -export function normalizeIntoStylingMap( - bindingValue: null | StylingMapArray, - newValues: {[key: string]: any} | string | null | undefined, - normalizeProps?: boolean): StylingMapArray { - const stylingMapArr: StylingMapArray = - Array.isArray(bindingValue) ? bindingValue : allocStylingMapArray(null); - stylingMapArr[StylingMapArrayIndex.RawValuePosition] = newValues; - - // because the new values may not include all the properties - // that the old ones had, all values are set to `null` before - // the new values are applied. This way, when flushed, the - // styling algorithm knows exactly what style/class values - // to remove from the element (since they are `null`). - for (let j = StylingMapArrayIndex.ValuesStartPosition; j < stylingMapArr.length; - j += StylingMapArrayIndex.TupleSize) { - setMapValue(stylingMapArr, j, null); - } - - let props: string[]|null = null; - let map: {[key: string]: any}|undefined|null; - let allValuesTrue = false; - if (typeof newValues === 'string') { // [class] bindings allow string values - props = splitOnWhitespace(newValues); - allValuesTrue = props !== null; - } else { - props = newValues ? Object.keys(newValues) : null; - map = newValues; - } - - if (props) { - for (let i = 0; i < props.length; i++) { - const prop = props[i]; - const newProp = normalizeProps ? hyphenate(prop) : prop; - const value = allValuesTrue ? true : map ![prop]; - addItemToStylingMap(stylingMapArr, newProp, value, true); - } - } - - return stylingMapArr; -} - -export function splitOnWhitespace(text: string): string[]|null { - let array: string[]|null = null; - let length = text.length; - let start = 0; - let foundChar = false; - for (let i = 0; i < length; i++) { - const char = text.charCodeAt(i); - if (char <= CharCode.SPACE) { - if (foundChar) { - if (array === null) array = []; - array.push(text.substring(start, i)); - foundChar = false; - } - start = i + 1; - } else { - foundChar = true; - } - } - if (foundChar) { - if (array === null) array = []; - array.push(text.substring(start, length)); - foundChar = false; - } - return array; -} - -// TODO (matsko|AndrewKushnir): refactor this once we figure out how to generate separate -// `input('class') + classMap()` instructions. -export function selectClassBasedInputName(inputs: PropertyAliases): string { - return inputs.hasOwnProperty('class') ? 'class' : 'className'; -} diff --git a/packages/core/src/util/ng_dev_mode.ts b/packages/core/src/util/ng_dev_mode.ts index 4a2819d7b6..70a9b65d8a 100644 --- a/packages/core/src/util/ng_dev_mode.ts +++ b/packages/core/src/util/ng_dev_mode.ts @@ -42,7 +42,6 @@ declare global { rendererSetClassName: number; rendererAddClass: number; rendererRemoveClass: number; - rendererCssText: number; rendererSetStyle: number; rendererRemoveStyle: number; rendererDestroy: number; @@ -52,17 +51,7 @@ declare global { rendererAppendChild: number; rendererInsertBefore: number; rendererCreateComment: number; - styleMap: number; - styleMapCacheMiss: number; - classMap: number; - classMapCacheMiss: number; - styleProp: number; - stylePropCacheMiss: number; - classProp: number; - classPropCacheMiss: number; flushStyling: number; - classesApplied: number; - stylesApplied: number; } } @@ -83,7 +72,6 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters { rendererSetClassName: 0, rendererAddClass: 0, rendererRemoveClass: 0, - rendererCssText: 0, rendererSetStyle: 0, rendererRemoveStyle: 0, rendererDestroy: 0, @@ -93,17 +81,7 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters { rendererAppendChild: 0, rendererInsertBefore: 0, rendererCreateComment: 0, - styleMap: 0, - styleMapCacheMiss: 0, - classMap: 0, - classMapCacheMiss: 0, - styleProp: 0, - stylePropCacheMiss: 0, - classProp: 0, - classPropCacheMiss: 0, flushStyling: 0, - classesApplied: 0, - stylesApplied: 0, }; // Make sure to refer to ngDevMode as ['ngDevMode'] for closure. diff --git a/packages/core/test/render3/styling_next/map_based_bindings_spec.ts b/packages/core/test/render3/styling_next/map_based_bindings_spec.ts deleted file mode 100644 index 6e5a55e3a0..0000000000 --- a/packages/core/test/render3/styling_next/map_based_bindings_spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {normalizeIntoStylingMap as createMap} from '../../../src/render3/util/styling_utils'; - -describe('map-based bindings', () => { - describe('StylingMapArray construction', () => { - it('should create a new StylingMapArray instance from a given value', () => { - createAndAssertValues(null, []); - createAndAssertValues(undefined, []); - createAndAssertValues({}, []); - createAndAssertValues({foo: 'bar'}, ['foo', 'bar']); - createAndAssertValues({bar: null}, ['bar', null]); - createAndAssertValues('', []); - createAndAssertValues('abc xyz', ['abc', true, 'xyz', true]); - createAndAssertValues([], []); - }); - - it('should list each entry in the context in alphabetical order', () => { - const value1 = {width: '200px', color: 'red', zIndex: -1}; - const map1 = createMap(null, value1); - expect(map1).toEqual([value1, 'color', 'red', 'width', '200px', 'zIndex', -1]); - - const value2 = 'yes no maybe'; - const map2 = createMap(null, value2); - expect(map2).toEqual([value2, 'maybe', true, 'no', true, 'yes', true]); - }); - - it('should patch an existing StylingMapArray entry with new values and retain the alphabetical order', - () => { - const value1 = {color: 'red'}; - const map1 = createMap(null, value1); - expect(map1).toEqual([value1, 'color', 'red']); - - const value2 = {backgroundColor: 'red', color: 'blue', opacity: '0.5'}; - const map2 = createMap(map1, value2); - expect(map1).toBe(map2); - expect(map1).toEqual( - [value2, 'backgroundColor', 'red', 'color', 'blue', 'opacity', '0.5']); - - const value3 = 'myClass'; - const map3 = createMap(null, value3); - expect(map3).toEqual([value3, 'myClass', true]); - - const value4 = 'yourClass everyonesClass myClass'; - const map4 = createMap(map3, value4); - expect(map3).toBe(map4); - expect(map4).toEqual([value4, 'everyonesClass', true, 'myClass', true, 'yourClass', true]); - }); - - it('should nullify old values that are not a part of the new set of values', () => { - const value1 = {color: 'red', fontSize: '20px'}; - const map1 = createMap(null, value1); - expect(map1).toEqual([value1, 'color', 'red', 'fontSize', '20px']); - - const value2 = {color: 'blue', borderColor: 'purple', opacity: '0.5'}; - const map2 = createMap(map1, value2); - expect(map2).toEqual( - [value2, 'borderColor', 'purple', 'color', 'blue', 'fontSize', null, 'opacity', '0.5']); - - const value3 = 'orange'; - const map3 = createMap(null, value3); - expect(map3).toEqual([value3, 'orange', true]); - - const value4 = 'apple banana'; - const map4 = createMap(map3, value4); - expect(map4).toEqual([value4, 'apple', true, 'banana', true, 'orange', null]); - }); - - it('should hyphenate property names ', () => { - const value1 = {fontSize: '50px', paddingTopLeft: '20px'}; - const map1 = createMap(null, value1, true); - expect(map1).toEqual([value1, 'font-size', '50px', 'padding-top-left', '20px']); - }); - }); -}); - -function createAndAssertValues(newValue: any, entries: any[]) { - const result = createMap(null, newValue); - expect(result).toEqual([newValue, ...entries]); -} diff --git a/packages/core/test/render3/styling_next/styling_context_spec.ts b/packages/core/test/render3/styling_next/styling_context_spec.ts deleted file mode 100644 index 89e914cbca..0000000000 --- a/packages/core/test/render3/styling_next/styling_context_spec.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {TStylingContext, TStylingNode} from '@angular/core/src/render3/interfaces/styling'; -import {registerBinding as _registerBinding} from '@angular/core/src/render3/styling/bindings'; -import {attachStylingDebugObject} from '@angular/core/src/render3/styling/styling_debug'; - -import {DEFAULT_GUARD_MASK_VALUE, allocTStylingContext} from '../../../src/render3/util/styling_utils'; - -function registerBinding( - context: TStylingContext, countId: number, sourceIndex: number, prop: string | null, - value: any) { - let tNode: TStylingNode = (context as any).tNode; - if (!tNode) { - tNode = (context as any).tNode = {flags: 0}; - } - _registerBinding(context, tNode, countId, sourceIndex, prop, value, false, false); -} - -describe('styling context', () => { - it('should register a series of entries into the context', () => { - const debug = makeContextWithDebug(false); - const context = debug.context; - expect(debug.entries).toEqual({}); - - registerBinding(context, 1, 0, 'width', '100px'); - expect(debug.entries['width']).toEqual({ - prop: 'width', - valuesCount: 1, - sanitizationRequired: false, - templateBitMask: buildGuardMask(), - hostBindingsBitMask: buildGuardMask(), - defaultValue: '100px', - sources: ['100px'], - }); - - registerBinding(context, 2, 0, 'width', 20); - expect(debug.entries['width']).toEqual({ - prop: 'width', - sanitizationRequired: false, - valuesCount: 2, - templateBitMask: buildGuardMask(2), - hostBindingsBitMask: buildGuardMask(), - defaultValue: '100px', - sources: [20, '100px'], - }); - - registerBinding(context, 3, 0, 'height', 10); - registerBinding(context, 4, 1, 'height', 15); - expect(debug.entries['height']).toEqual({ - prop: 'height', - valuesCount: 3, - sanitizationRequired: false, - templateBitMask: buildGuardMask(3), - hostBindingsBitMask: buildGuardMask(4), - defaultValue: null, - sources: [10, 15, null], - }); - }); - - it('should only register the same binding index once per property', () => { - const debug = makeContextWithDebug(false); - const context = debug.context; - expect(debug.entries).toEqual({}); - - registerBinding(context, 1, 0, 'width', 123); - registerBinding(context, 1, 0, 'width', 123); - expect(debug.entries['width']).toEqual({ - prop: 'width', - valuesCount: 2, - sanitizationRequired: false, - templateBitMask: buildGuardMask(1), - hostBindingsBitMask: buildGuardMask(), - defaultValue: null, - sources: [123, null], - }); - }); - - it('should overwrite a default value for an entry only if it is non-null', () => { - const debug = makeContextWithDebug(false); - const context = debug.context; - - registerBinding(context, 1, 0, 'width', null); - const x = debug.entries['width']; - expect(debug.entries['width']).toEqual({ - prop: 'width', - valuesCount: 1, - sanitizationRequired: false, - templateBitMask: buildGuardMask(), - hostBindingsBitMask: buildGuardMask(), - defaultValue: null, - sources: [null] - }); - - registerBinding(context, 1, 0, 'width', '100px'); - expect(debug.entries['width']).toEqual({ - prop: 'width', - valuesCount: 1, - sanitizationRequired: false, - templateBitMask: buildGuardMask(), - hostBindingsBitMask: buildGuardMask(), - defaultValue: '100px', - sources: ['100px'] - }); - - registerBinding(context, 1, 0, 'width', '200px'); - expect(debug.entries['width']).toEqual({ - prop: 'width', - valuesCount: 1, - sanitizationRequired: false, - templateBitMask: buildGuardMask(), - hostBindingsBitMask: buildGuardMask(), - defaultValue: '100px', - sources: ['100px'] - }); - }); -}); - -function makeContextWithDebug(isClassBased: boolean) { - const ctx = allocTStylingContext(null, false); - const tNode: TStylingNode = {flags: 0}; - (ctx as any).tNode = ctx; - return attachStylingDebugObject(ctx, tNode, isClassBased); -} - -function buildGuardMask(...bindingIndices: number[]) { - let mask = DEFAULT_GUARD_MASK_VALUE; - for (let i = 0; i < bindingIndices.length; i++) { - mask |= 1 << bindingIndices[i]; - } - return mask; -} diff --git a/packages/core/test/render3/styling_next/styling_debug_spec.ts b/packages/core/test/render3/styling_next/styling_debug_spec.ts deleted file mode 100644 index 75cdf5decf..0000000000 --- a/packages/core/test/render3/styling_next/styling_debug_spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import {TStylingNode} from '@angular/core/src/render3/interfaces/styling'; -import {registerBinding} from '@angular/core/src/render3/styling/bindings'; -import {NodeStylingDebug, attachStylingDebugObject} from '@angular/core/src/render3/styling/styling_debug'; -import {allocTStylingContext} from '@angular/core/src/render3/util/styling_utils'; - -describe('styling debugging tools', () => { - describe('NodeStylingDebug', () => { - it('should list out each of the values in the context paired together with the provided data', - () => { - if (isIE()) return; - - const values = makeContextWithDebug(false); - const context = values.context; - const tNode = values.tNode; - - const data: any[] = []; - const d = new NodeStylingDebug(context, tNode, data, false); - - registerBinding(context, tNode, 0, 0, 'width', null, false, false); - expect(d.summary).toEqual({ - width: { - prop: 'width', - value: null, - bindingIndex: null, - }, - }); - - registerBinding(context, tNode, 0, 0, 'width', '100px', false, false); - expect(d.summary).toEqual({ - width: { - prop: 'width', - value: '100px', - bindingIndex: null, - }, - }); - - const someBindingIndex1 = 1; - data[someBindingIndex1] = '200px'; - - registerBinding(context, tNode, 0, 0, 'width', someBindingIndex1, false, false); - expect(d.summary).toEqual({ - width: { - prop: 'width', - value: '200px', - bindingIndex: someBindingIndex1, - }, - }); - - const someBindingIndex2 = 2; - data[someBindingIndex2] = '500px'; - - registerBinding(context, tNode, 0, 1, 'width', someBindingIndex2, false, false); - expect(d.summary).toEqual({ - width: { - prop: 'width', - value: '200px', - bindingIndex: someBindingIndex1, - }, - }); - }); - }); -}); - -function makeContextWithDebug(isClassBased: boolean) { - const context = allocTStylingContext(null, false); - const tNode = createTStylingNode(); - attachStylingDebugObject(context, tNode, isClassBased); - return {context, tNode}; -} - -function createTStylingNode(): TStylingNode { - return {flags: 0}; -} - -function isIE() { - // note that this only applies to older IEs (not edge) - return typeof window !== 'undefined' && (window as any).document['documentMode'] ? true : false; -} diff --git a/packages/core/test/render3/util/styling_utils_spec.ts b/packages/core/test/render3/util/styling_utils_spec.ts deleted file mode 100644 index 3267e680ce..0000000000 --- a/packages/core/test/render3/util/styling_utils_spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {splitOnWhitespace} from '@angular/core/src/render3/util/styling_utils'; - -describe('styling_utils', () => { - describe('splitOnWhitespace', () => { - it('should treat empty strings as null', () => { - expect(splitOnWhitespace('')).toEqual(null); - expect(splitOnWhitespace(' ')).toEqual(null); - expect(splitOnWhitespace(' \n\r\t ')).toEqual(null); - }); - - it('should split strings into parts', () => { - expect(splitOnWhitespace('a\nb\rc')).toEqual(['a', 'b', 'c']); - expect(splitOnWhitespace('\ta-long\nb-long\rc-long ')).toEqual([ - 'a-long', 'b-long', 'c-long' - ]); - }); - }); -}); \ No newline at end of file