From 41560b47c46a3fd50c935cd6661c690bad71f648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Fri, 1 Nov 2019 15:53:50 -0700 Subject: [PATCH] refactor(ivy): move all styling configurations into `TNodeFlags` (#33540) This patch gets rid of the configuration settings present in the `TStylingContext` array that is used within the styling algorithm for `[style]`, `[style.prop]`, `[class]` and `[class.name]` bindings. These configurations now all live inside of the `TNodeFlags`. PR Close #33540 --- integration/_payload-limits.json | 2 +- .../src/render3/instructions/lview_debug.ts | 4 +- .../core/src/render3/instructions/styling.ts | 103 +++++++++----- packages/core/src/render3/interfaces/node.ts | 116 ++++++++++++++- .../core/src/render3/interfaces/styling.ts | 132 ++---------------- packages/core/src/render3/styling/bindings.ts | 129 +++++++++-------- .../core/src/render3/styling/styling_debug.ts | 59 +++++--- .../core/src/render3/util/styling_utils.ts | 61 ++++---- .../bundling/todo/bundle.golden_symbols.json | 9 +- .../styling_next/styling_context_spec.ts | 17 ++- .../styling_next/styling_debug_spec.ts | 29 ++-- 11 files changed, 369 insertions(+), 292 deletions(-) diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 995884df63..de6ba07073 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -39,7 +39,7 @@ "master": { "uncompressed": { "runtime-es2015": 2289, - "main-es2015": 265613, + "main-es2015": 268331, "polyfills-es2015": 36808, "5-es2015": 751 } diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index 1a435c1e9e..78ce66d8e4 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -375,10 +375,10 @@ export function buildDebugNode(tNode: TNode, lView: LView, nodeIndex: number): D const native = unwrapRNode(rawValue); const componentLViewDebug = toDebug(readLViewValue(rawValue)); const styles = isStylingContext(tNode.styles) ? - new NodeStylingDebug(tNode.styles as any as TStylingContext, lView, false) : + new NodeStylingDebug(tNode.styles as any as TStylingContext, tNode, lView, false) : null; const classes = isStylingContext(tNode.classes) ? - new NodeStylingDebug(tNode.classes as any as TStylingContext, lView, true) : + new NodeStylingDebug(tNode.classes as any as TStylingContext, tNode, lView, true) : null; return { html: toHtml(native), diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts index 5e8c6c4dd1..1acf61c58a 100644 --- a/packages/core/src/render3/instructions/styling.ts +++ b/packages/core/src/render3/instructions/styling.ts @@ -11,7 +11,7 @@ import {throwErrorIfNoChangesMode} from '../errors'; import {setInputsForProperty} from '../instructions/shared'; import {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node'; import {RElement} from '../interfaces/renderer'; -import {StylingMapArray, StylingMapArrayIndex, TStylingConfig, TStylingContext} from '../interfaces/styling'; +import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from '../interfaces/styling'; import {isDirectiveHost} from '../interfaces/type_checks'; import {LView, RENDERER, TVIEW, TView} from '../interfaces/view'; import {getActiveDirectiveId, getCheckNoChangesMode, getCurrentStyleSanitizer, getLView, getSelectedIndex, incrementBindingIndex, nextBindingIndex, resetCurrentStyleSanitizer, setCurrentStyleSanitizer, setElementExitFn} from '../state'; @@ -95,9 +95,21 @@ export function stylePropInternal( // 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; - const updated = - stylingProp(elementIndex, bindingIndex, prop, resolveStylePropValue(value, suffix), false); + // 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) { @@ -127,8 +139,20 @@ export function ɵɵclassProp(className: string, value: boolean | null): void { // still needs to be incremented because all styling binding values // are stored inside of the lView. const bindingIndex = nextBindingIndex(); + const lView = getLView(); + const elementIndex = getSelectedIndex(); + const tNode = getTNode(elementIndex, lView); + const firstUpdatePass = lView[TVIEW].firstUpdatePass; - const updated = stylingProp(getSelectedIndex(), bindingIndex, className, value, true); + // 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.hasClassPropBindings); + patchHostStylingFlag(tNode, isHostStyling(), true); + } + + const updated = stylingProp(tNode, firstUpdatePass, lView, bindingIndex, className, value, true); if (ngDevMode) { ngDevMode.classProp++; if (updated) { @@ -148,25 +172,15 @@ export function ɵɵclassProp(className: string, value: boolean | null): void { * present together). */ function stylingProp( - elementIndex: number, bindingIndex: number, prop: string, + 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 lView = getLView(); - const firstUpdatePass = lView[TVIEW].firstUpdatePass; - const tNode = getTNode(elementIndex, lView); const native = getNativeByTNode(tNode, lView) as RElement; const context = isClassBased ? getClassesContext(tNode) : getStylesContext(tNode); const sanitizer = isClassBased ? null : getCurrentStyleSanitizer(); - // 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(context, TStylingConfig.HasPropBindings); - } - // [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 @@ -180,11 +194,12 @@ function stylingProp( // Direct Apply Case: bypass context resolution and apply the // style/class value directly to the element - if (allowDirectStyling(context, firstUpdatePass)) { + if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) { const sanitizerToUse = isClassBased ? null : sanitizer; const renderer = getRenderer(tNode, lView); updated = applyStylingValueDirectly( - renderer, context, native, lView, bindingIndex, prop, value, isClassBased, sanitizerToUse); + renderer, context, tNode, native, lView, bindingIndex, prop, value, isClassBased, + sanitizerToUse); if (sanitizerToUse) { // it's important we remove the current style sanitizer once the @@ -199,11 +214,11 @@ function stylingProp( const directiveIndex = getActiveDirectiveId(); if (isClassBased) { updated = updateClassViaContext( - context, lView, native, directiveIndex, prop, bindingIndex, + context, tNode, lView, native, directiveIndex, prop, bindingIndex, value as string | boolean | null, false, firstUpdatePass); } else { updated = updateStyleViaContext( - context, lView, native, directiveIndex, prop, bindingIndex, + context, tNode, lView, native, directiveIndex, prop, bindingIndex, value as string | SafeValue | null, sanitizer, false, firstUpdatePass); } @@ -245,15 +260,24 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu // 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 (!isHostStyling() && hasDirectiveInput && styles !== NO_CHANGE) { + if (!hostBindingsMode && hasDirectiveInput && styles !== NO_CHANGE) { updateDirectiveInputValue(context, lView, tNode, bindingIndex, styles, false, firstUpdatePass); styles = 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.hasStyleMapBindings); + patchHostStylingFlag(tNode, isHostStyling(), false); + } + stylingMap( context, tNode, firstUpdatePass, lView, bindingIndex, styles, false, hasDirectiveInput); } @@ -299,15 +323,24 @@ export function classMapInternal( // 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 (!isHostStyling() && hasDirectiveInput && classes !== NO_CHANGE) { + 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); } @@ -336,20 +369,13 @@ function stylingMap( throwErrorIfNoChangesMode(false, oldValue, value); } - // 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(context, TStylingConfig.HasMapBindings); - } - // Direct Apply Case: bypass context resolution and apply the // style/class map values directly to the element - if (allowDirectStyling(context, firstUpdatePass)) { + if (allowDirectStyling(tNode, isClassBased, firstUpdatePass)) { const sanitizerToUse = isClassBased ? null : sanitizer; const renderer = getRenderer(tNode, lView); applyStylingMapDirectly( - renderer, context, native, lView, bindingIndex, value, isClassBased, sanitizerToUse, + renderer, context, tNode, native, lView, bindingIndex, value, isClassBased, sanitizerToUse, valueHasChanged, hasDirectiveInput); if (sanitizerToUse) { // it's important we remove the current style sanitizer once the @@ -368,12 +394,12 @@ function stylingMap( // value to the element. if (isClassBased) { updateClassViaContext( - context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, + context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, valueHasChanged, firstUpdatePass); } else { updateStyleViaContext( - context, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, sanitizer, - valueHasChanged, firstUpdatePass); + context, tNode, lView, native, directiveIndex, null, bindingIndex, stylingMapArr, + sanitizer, valueHasChanged, firstUpdatePass); } setElementExitFn(stylingApply); @@ -463,7 +489,7 @@ function stylingApply(): void { const classesContext = isStylingContext(tNode.classes) ? tNode.classes as TStylingContext : null; const stylesContext = isStylingContext(tNode.styles) ? tNode.styles as TStylingContext : null; flushStyling( - renderer, lView, classesContext, stylesContext, native, directiveIndex, sanitizer, + renderer, lView, tNode, classesContext, stylesContext, native, directiveIndex, sanitizer, tView.firstUpdatePass); resetCurrentStyleSanitizer(); } @@ -541,7 +567,7 @@ function getContext(tNode: TNode, isClassBased: boolean): TStylingContext { const hasDirectives = isDirectiveHost(tNode); context = allocTStylingContext(context as StylingMapArray | null, hasDirectives); if (ngDevMode) { - attachStylingDebugObject(context as TStylingContext, isClassBased); + attachStylingDebugObject(context as TStylingContext, tNode, isClassBased); } if (isClassBased) { @@ -582,3 +608,10 @@ function resolveStylePropValue( 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/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index bc44f9ad70..316cd3a141 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -67,19 +67,121 @@ export const enum TNodeFlags { /** Bit #6 - This bit is set if the node has any "style" inputs */ hasStyleInput = 0x20, - /** Bit #7 - This bit is set if the node has initial styling */ - hasInitialStyling = 0x40, - - /** Bit #8 - This bit is set if the node has been detached by i18n */ - isDetached = 0x80, + /** Bit #7 This bit is set if the node has been detached by i18n */ + isDetached = 0x40, /** - * Bit #9 - This bit is set if the node has directives with host bindings. + * Bit #8 - This bit is set if the node has directives with host bindings. * * This flags allows us to guard host-binding logic and invoke it only on nodes * that actually have directives with host bindings. */ - hasHostBindings = 0x100, + hasHostBindings = 0x80, + + /** Bit #9 - This bit is set if the node has initial styling */ + hasInitialStyling = 0x100, + + /** + * Bit #10 - Whether or not there are class-based map bindings present. + * + * Examples include: + * 1. `
` + * 2. `@HostBinding('class') x` + */ + hasClassMapBindings = 0x200, + + /** + * Bit #11 - Whether or not there are any class-based prop bindings present. + * + * Examples include: + * 1. `
` + * 2. `@HostBinding('class.name') x` + */ + hasClassPropBindings = 0x400, + + /** + * Bit #12 - whether or not there are any active [class] and [class.name] bindings + */ + hasClassPropAndMapBindings = hasClassMapBindings | hasClassPropBindings, + + /** + * Bit #13 - Whether or not the context contains one or more class-based template bindings. + * + * Examples include: + * 1. `
` + * 2. `
` + */ + hasTemplateClassBindings = 0x800, + + /** + * Bit #14 - Whether or not the context contains one or more class-based host bindings. + * + * Examples include: + * 1. `@HostBinding('class') x` + * 2. `@HostBinding('class.name') x` + */ + hasHostClassBindings = 0x1000, + + /** + * Bit #15 - Whether or not there are two or more sources for a class property in the context. + * + * Examples include: + * 1. prop + prop: `
` + * 2. map + prop: `
` + * 3. map + map: `
` + */ + hasDuplicateClassBindings = 0x2000, + + /** + * Bit #16 - Whether or not there are style-based map bindings present. + * + * Examples include: + * 1. `
` + * 2. `@HostBinding('style') x` + */ + hasStyleMapBindings = 0x4000, + + /** + * Bit #17 - Whether or not there are any style-based prop bindings present. + * + * Examples include: + * 1. `
` + * 2. `@HostBinding('style.prop') x` + */ + hasStylePropBindings = 0x8000, + + /** + * Bit #18 - whether or not there are any active [style] and [style.prop] bindings + */ + hasStylePropAndMapBindings = hasStyleMapBindings | hasStylePropBindings, + + /** + * Bit #19 - Whether or not the context contains one or more style-based template bindings. + * + * Examples include: + * 1. `
` + * 2. `
` + */ + hasTemplateStyleBindings = 0x10000, + + /** + * Bit #20 - Whether or not the context contains one or more style-based host bindings. + * + * Examples include: + * 1. `@HostBinding('style') x` + * 2. `@HostBinding('style.prop') x` + */ + hasHostStyleBindings = 0x20000, + + /** + * Bit #21 - Whether or not there are two or more sources for a style property in the context. + * + * Examples include: + * 1. prop + prop: `
` + * 2. map + prop: `
` + * 3. map + map: `
` + */ + hasDuplicateStyleBindings = 0x40000, } /** diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index 7a4b4b202c..04a88399ed 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -6,9 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; + +import {TNodeFlags} from './node'; import {ProceduralRenderer3, RElement, Renderer3} from './renderer'; import {LView} from './view'; + /** * -------- * @@ -75,7 +78,6 @@ import {LView} from './view'; * // ... * //
* tNode.styles = [ - * 0, // the context config value (see `TStylingContextConfig`) * 1, // the total amount of sources present (only `1` b/c there are only template * bindings) * [null], // initial values array (an instance of `StylingMapArray`) @@ -323,9 +325,6 @@ import {LView} from './view'; */ export interface TStylingContext extends Array { - /** Configuration data for the context */ - [TStylingContextIndex.ConfigPosition]: TStylingConfig; - /** The total amount of sources present in the context */ [TStylingContextIndex.TotalSourcesPosition]: number; @@ -333,125 +332,13 @@ export interface TStylingContext extends [TStylingContextIndex.InitialStylingValuePosition]: StylingMapArray; } -/** - * A series of flags used to configure the config value present within an instance of - * `TStylingContext`. - */ -export const enum TStylingConfig { - /** - * The initial state of the styling context config. - */ - Initial = 0b000000, - - /** - * Whether or not there are any directives on this element. - * - * This is used so that certain performance optimizations can - * take place (e.g. direct style/class binding application). - * - * Note that the presence of this flag doesn't guarantee the - * presence of host-level style or class bindings within any - * of the active directives on the element. - * - * Examples include: - * 1. `
` - * 2. `
` - * 3. `` - * 4. `` - */ - HasDirectives = 0b000001, - - /** - * Whether or not there are prop-based bindings present. - * - * Examples include: - * 1. `
` - * 2. `
` - * 3. `@HostBinding('style.prop') x` - * 4. `@HostBinding('class.prop') x` - */ - HasPropBindings = 0b000010, - - /** - * Whether or not there are map-based bindings present. - * - * Examples include: - * 1. `
` - * 2. `
` - * 3. `@HostBinding('style') x` - * 4. `@HostBinding('class') x` - */ - HasMapBindings = 0b000100, - - /** - * Whether or not there are map-based and prop-based bindings present. - * - * Examples include: - * 1. `
` - * 2. `
` - * 3. `
` - * 4. `
` - */ - HasPropAndMapBindings = HasPropBindings | HasMapBindings, - - /** - * Whether or not there are two or more sources for a single property in the context. - * - * Examples include: - * 1. prop + prop: `
` - * 2. map + prop: `
` - * 3. map + map: `
` - */ - HasCollisions = 0b001000, - - /** - * Whether or not the context contains initial styling values. - * - * Examples include: - * 1. `
` - * 2. `
` - * 3. `@Directive({ host: { 'style': 'width:200px' } })` - * 4. `@Directive({ host: { 'class': 'one two three' } })` - */ - HasInitialStyling = 0b0010000, - - /** - * Whether or not the context contains one or more template bindings. - * - * Examples include: - * 1. `
` - * 2. `
` - * 3. `
` - * 4. `
` - */ - HasTemplateBindings = 0b0100000, - - /** - * Whether or not the context contains one or more host bindings. - * - * Examples include: - * 1. `@HostBinding('style') x` - * 2. `@HostBinding('style.width') x` - * 3. `@HostBinding('class') x` - * 4. `@HostBinding('class.name') x` - */ - HasHostBindings = 0b1000000, - - /** A Mask of all the configurations */ - Mask = 0b1111111, - - /** Total amount of configuration bits used */ - TotalBits = 7, -} - /** * An index of position and offset values used to navigate the `TStylingContext`. */ export const enum TStylingContextIndex { - ConfigPosition = 0, - TotalSourcesPosition = 1, - InitialStylingValuePosition = 2, - ValuesStartPosition = 3, + TotalSourcesPosition = 0, + InitialStylingValuePosition = 1, + ValuesStartPosition = 2, // each tuple entry in the context // (config, templateBitGuard, hostBindingBitGuard, prop, ...bindings||default-value) @@ -577,3 +464,10 @@ export const enum StylingMapsSyncMode { /** Only check to see if a value was set somewhere in each map */ CheckValuesOnly = 0b10000, } + +/** + * Simplified `TNode` interface for styling-related code. + * + * The styling algorithm code only needs access to `flags`. + */ +export interface TStylingNode { flags: TNodeFlags; } diff --git a/packages/core/src/render3/styling/bindings.ts b/packages/core/src/render3/styling/bindings.ts index 7a01d81b56..66dc7ade51 100644 --- a/packages/core/src/render3/styling/bindings.ts +++ b/packages/core/src/render3/styling/bindings.ts @@ -8,10 +8,11 @@ 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, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from '../interfaces/styling'; +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, getConfig, 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 {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'; @@ -27,7 +28,7 @@ const VALUE_IS_EXTERNALLY_MODIFIED = {}; * * 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 `TNode` and each element instance + * There is only one `TStylingContext` per `TStylingNode` and each element instance * will update its style/class binding values in concert with the styling * context. * @@ -54,8 +55,8 @@ const STYLING_INDEX_FOR_MAP_BINDING = 0; * and the bit mask values to be in sync). */ export function updateClassViaContext( - context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number, - prop: string | null, bindingIndex: number, + 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; @@ -67,8 +68,8 @@ export function updateClassViaContext( // is aware of the binding even if things change after the first update pass. if (firstUpdatePass || value !== NO_CHANGE) { const updated = updateBindingData( - context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, false, - firstUpdatePass); + 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 @@ -93,8 +94,8 @@ export function updateClassViaContext( * and the bit mask values to be in sync). */ export function updateStyleViaContext( - context: TStylingContext, data: LStylingData, element: RElement, directiveIndex: number, - prop: string | null, bindingIndex: number, + 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; @@ -109,8 +110,8 @@ export function updateStyleViaContext( true : (sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false); const updated = updateBindingData( - context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, - sanitizationRequired, firstUpdatePass); + 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 @@ -136,11 +137,14 @@ export function updateStyleViaContext( * @returns whether or not the binding value was updated in the `LStylingData`. */ function updateBindingData( - context: TStylingContext, data: LStylingData, counterIndex: number, sourceIndex: number, - prop: string | null, bindingIndex: number, + 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): boolean { + 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` @@ -148,19 +152,18 @@ function updateBindingData( // 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, counterIndex, sourceIndex, prop, bindingIndex, sanitizationRequired); - patchConfig( - context, - hostBindingsMode ? TStylingConfig.HasHostBindings : TStylingConfig.HasTemplateBindings); + registerBinding( + context, tNode, counterIndex, sourceIndex, prop, bindingIndex, sanitizationRequired, + isClassBased); } const changed = forceUpdate || hasValueChanged(data[bindingIndex], value); if (changed) { setValue(data, bindingIndex, value); - const doSetValuesAsStale = (getConfig(context) & TStylingConfig.HasHostBindings) && - !hostBindingsMode && (prop ? !value : true); + const doSetValuesAsStale = + hasConfig(tNode, hostBindingsFlag) && !hostBindingsMode && (prop ? !value : true); if (doSetValuesAsStale) { - renderHostBindingsAsStale(context, data, prop); + renderHostBindingsAsStale(context, tNode, data, prop, isClassBased); } } return changed; @@ -178,10 +181,13 @@ function updateBindingData( * binding changes. */ function renderHostBindingsAsStale( - context: TStylingContext, data: LStylingData, prop: string | null): void { + context: TStylingContext, tNode: TStylingNode, data: LStylingData, prop: string | null, + isClassBased: boolean): void { const valuesCount = getValuesCount(context); - if (prop !== null && hasConfig(context, TStylingConfig.HasPropBindings)) { + const hostBindingsFlag = + isClassBased ? TNodeFlags.hasHostClassBindings : TNodeFlags.hasHostStyleBindings; + if (prop !== null && hasConfig(tNode, hostBindingsFlag)) { const itemsPerRow = TStylingContextIndex.BindingsStartOffset + valuesCount; let i = TStylingContextIndex.ValuesStartPosition; @@ -208,7 +214,9 @@ function renderHostBindingsAsStale( } } - if (hasConfig(context, TStylingConfig.HasMapBindings)) { + 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 @@ -253,8 +261,9 @@ function renderHostBindingsAsStale( * much the same as prop-based bindings, but, their property name value is set as `[MAP]`. */ export function registerBinding( - context: TStylingContext, countId: number, sourceIndex: number, prop: string | null, - bindingValue: number | null | string | boolean, sanitizationRequired?: boolean): void { + 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; @@ -268,6 +277,8 @@ export function registerBinding( totalSources++; } + const collisionFlag = + isClassBased ? TNodeFlags.hasDuplicateClassBindings : TNodeFlags.hasDuplicateStyleBindings; const isBindingIndexValue = typeof bindingValue === 'number'; const entriesPerRow = TStylingContextIndex.BindingsStartOffset + getValuesCount(context); let i = TStylingContextIndex.ValuesStartPosition; @@ -279,7 +290,7 @@ export function registerBinding( if (prop < p) { allocateNewContextEntry(context, i, prop, sanitizationRequired); } else if (isBindingIndexValue) { - patchConfig(context, TStylingConfig.HasCollisions); + patchConfig(tNode, collisionFlag); } addBindingIntoContext(context, i, bindingValue, countId, sourceIndex); found = true; @@ -405,7 +416,7 @@ function addNewSourceColumn(context: TStylingContext): void { * (i.e. the `bitMask` and `counter` values for styles and classes will be cleared). */ export function flushStyling( - renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, + renderer: Renderer3 | ProceduralRenderer3 | null, data: LStylingData, tNode: TStylingNode, classesContext: TStylingContext | null, stylesContext: TStylingContext | null, element: RElement, directiveIndex: number, styleSanitizer: StyleSanitizeFn | null, firstUpdatePass: boolean): void { @@ -415,22 +426,22 @@ export function flushStyling( const hostBindingsMode = isHostStylingActive(state.sourceIndex); if (stylesContext) { - firstUpdatePass && syncContextInitialStyling(stylesContext); + firstUpdatePass && syncContextInitialStyling(stylesContext, tNode, false); if (state.stylesBitMask !== 0) { applyStylingViaContext( - stylesContext, renderer, element, data, state.stylesBitMask, setStyle, styleSanitizer, - hostBindingsMode); + stylesContext, tNode, renderer, element, data, state.stylesBitMask, setStyle, + styleSanitizer, hostBindingsMode, false); } } if (classesContext) { - firstUpdatePass && syncContextInitialStyling(classesContext); + firstUpdatePass && syncContextInitialStyling(classesContext, tNode, true); if (state.classesBitMask !== 0) { applyStylingViaContext( - classesContext, renderer, element, data, state.classesBitMask, setClass, null, - hostBindingsMode); + classesContext, tNode, renderer, element, data, state.classesBitMask, setClass, null, + hostBindingsMode, true); } } @@ -492,10 +503,11 @@ export function flushStyling( * ] * ``` */ -function syncContextInitialStyling(context: TStylingContext): void { +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, getStylingMapArray(context) !); + updateInitialStylingOnContext(context, tNode, getStylingMapArray(context) !, isClassBased); } /** @@ -513,7 +525,8 @@ function syncContextInitialStyling(context: TStylingContext): void { * update itself with the complete initial styling for the element. */ function updateInitialStylingOnContext( - context: TStylingContext, initialStyling: StylingMapArray): void { + 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; @@ -524,13 +537,13 @@ function updateInitialStylingOnContext( const value = getMapValue(initialStyling, i); if (value) { const prop = getMapProp(initialStyling, i); - registerBinding(context, COUNT_ID_FOR_STYLING, 0, prop, value, false); + registerBinding(context, tNode, COUNT_ID_FOR_STYLING, 0, prop, value, false, isClassBased); hasInitialStyling = true; } } if (hasInitialStyling) { - patchConfig(context, TStylingConfig.HasInitialStyling); + patchConfig(tNode, TNodeFlags.hasInitialStyling); } } @@ -562,14 +575,17 @@ function updateInitialStylingOnContext( * the styles and classes contexts). */ export function applyStylingViaContext( - context: TStylingContext, renderer: Renderer3 | ProceduralRenderer3 | null, element: RElement, - bindingData: LStylingData, bitMaskValue: number | boolean, applyStylingFn: ApplyStylingFn, - sanitizer: StyleSanitizeFn | null, hostBindingsMode: boolean): void { + 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; - if (hasConfig(context, TStylingConfig.HasMapBindings)) { + const mapBindingsFlag = + isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; + if (hasConfig(tNode, mapBindingsFlag)) { stylingMapsSyncFn = getStylingMapsSyncFn(); const mapsGuardMask = getGuardMask(context, TStylingContextIndex.ValuesStartPosition, hostBindingsMode); @@ -585,7 +601,7 @@ export function applyStylingViaContext( totalBindingsToVisit = valuesCount - 1; } - let i = getPropValuesStartPosition(context); + let i = getPropValuesStartPosition(context, tNode, isClassBased); while (i < context.length) { const guardMask = getGuardMask(context, i, hostBindingsMode); if (bitMask & guardMask) { @@ -681,14 +697,13 @@ export function applyStylingViaContext( * @returns whether or not the styling map was applied to the element. */ export function applyStylingMapDirectly( - renderer: any, context: TStylingContext, element: RElement, data: LStylingData, - bindingIndex: number, value: {[key: string]: any} | string | null, isClassBased: boolean, - sanitizer?: StyleSanitizeFn | null, forceUpdate?: boolean, - bindingValueContainsInitial?: boolean): void { + 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 config = getConfig(context); - const hasInitial = config & TStylingConfig.HasInitialStyling; + const hasInitial = hasConfig(tNode, TNodeFlags.hasInitialStyling); const initialValue = hasInitial && !bindingValueContainsInitial ? getInitialStylingValue(context) : null; setValue(data, bindingIndex, value); @@ -707,7 +722,9 @@ export function applyStylingMapDirectly( // 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. - let writeToAttrDirectly = !(config & TStylingConfig.HasPropBindings); + const propBindingsFlag = + isClassBased ? TNodeFlags.hasClassPropBindings : TNodeFlags.hasStylePropBindings; + let writeToAttrDirectly = !hasConfig(tNode, propBindingsFlag); if (writeToAttrDirectly && checkIfExternallyModified(element as HTMLElement, cachedValue, isClassBased)) { writeToAttrDirectly = false; @@ -818,8 +835,8 @@ export function writeStylingValueDirectly( * @returns whether or not the prop/value styling was applied to the element. */ export function applyStylingValueDirectly( - renderer: any, context: TStylingContext, element: RElement, data: LStylingData, - bindingIndex: number, prop: string, value: any, isClassBased: boolean, + 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)) { @@ -830,7 +847,9 @@ export function applyStylingValueDirectly( applied = applyStylingValue(renderer, element, prop, value, applyFn, bindingIndex, sanitizer); // case 2: find the matching property in a styling map and apply the detected value - if (!applied && hasConfig(context, TStylingConfig.HasMapBindings)) { + 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 ? @@ -839,7 +858,7 @@ export function applyStylingValueDirectly( } // case 3: apply the initial value (if it exists) - if (!applied && hasConfig(context, TStylingConfig.HasInitialStyling)) { + if (!applied && hasConfig(tNode, TNodeFlags.hasInitialStyling)) { const map = getStylingMapArray(context); applied = map ? findAndApplyMapValue(renderer, element, applyFn, map, prop, bindingIndex) : false; diff --git a/packages/core/src/render3/styling/styling_debug.ts b/packages/core/src/render3/styling/styling_debug.ts index 13e97065d1..3969e7731b 100644 --- a/packages/core/src/render3/styling/styling_debug.ts +++ b/packages/core/src/render3/styling/styling_debug.ts @@ -7,8 +7,9 @@ */ import {createProxy} from '../../debug/proxy'; import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; +import {TNodeFlags} from '../interfaces/node'; import {RElement} from '../interfaces/renderer'; -import {ApplyStylingFn, LStylingData, TStylingConfig, TStylingContext, TStylingContextIndex} from '../interfaces/styling'; +import {ApplyStylingFn, LStylingData, TStylingContext, TStylingContextIndex, TStylingNode} from '../interfaces/styling'; 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'; @@ -52,7 +53,7 @@ export interface DebugStylingContext { /** - * A debug/testing-oriented summary of `TStylingConfig`. + * A debug/testing-oriented summary of all styling information in `TNode.flags`. */ export interface DebugStylingConfig { hasMapBindings: boolean; // @@ -150,8 +151,9 @@ export interface DebugNodeStylingEntry { /** * Instantiates and attaches an instance of `TStylingContextDebug` to the provided context */ -export function attachStylingDebugObject(context: TStylingContext, isClassBased: boolean) { - const debug = new TStylingContextDebug(context, isClassBased); +export function attachStylingDebugObject( + context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) { + const debug = new TStylingContextDebug(context, tNode, isClassBased); attachDebugObject(context, debug); return debug; } @@ -163,9 +165,11 @@ export function attachStylingDebugObject(context: TStylingContext, isClassBased: * application has `ngDevMode` activated. */ class TStylingContextDebug implements DebugStylingContext { - constructor(public readonly context: TStylingContext, private _isClassBased: boolean) {} + constructor( + public readonly context: TStylingContext, private _tNode: TStylingNode, + private _isClassBased: boolean) {} - get config(): DebugStylingConfig { return buildConfig(this.context); } + get config(): DebugStylingConfig { return buildConfig(this._tNode, this._isClassBased); } /** * Returns a detailed summary of each styling entry in the context. @@ -176,7 +180,7 @@ class TStylingContextDebug implements DebugStylingContext { const context = this.context; const totalColumns = getValuesCount(context); const entries: {[prop: string]: DebugStylingContextEntry} = {}; - const start = getPropValuesStartPosition(context); + const start = getPropValuesStartPosition(context, this._tNode, this._isClassBased); let i = start; while (i < context.length) { const prop = getProp(context, i); @@ -351,10 +355,10 @@ export class NodeStylingDebug implements DebugNodeStyling { private _debugContext: DebugStylingContext; constructor( - context: TStylingContext|DebugStylingContext, private _data: LStylingData, - private _isClassBased: boolean) { + context: TStylingContext|DebugStylingContext, private _tNode: TStylingNode, + private _data: LStylingData, private _isClassBased: boolean) { this._debugContext = isStylingContext(context) ? - new TStylingContextDebug(context as TStylingContext, _isClassBased) : + new TStylingContextDebug(context as TStylingContext, _tNode, _isClassBased) : (context as DebugStylingContext); } @@ -421,7 +425,7 @@ export class NodeStylingDebug implements DebugNodeStyling { }); } - get config() { return buildConfig(this.context.context); } + 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. @@ -447,7 +451,7 @@ export class NodeStylingDebug implements DebugNodeStyling { private _convertMapBindingsToStylingMapArrays(data: LStylingData) { const context = this.context.context; - const limit = getPropValuesStartPosition(context); + const limit = getPropValuesStartPosition(context, this._tNode, this._isClassBased); for (let i = TStylingContextIndex.ValuesStartPosition + TStylingContextIndex.BindingsStartOffset; i < limit; i++) { @@ -467,7 +471,9 @@ export class NodeStylingDebug implements DebugNodeStyling { // 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 hasMaps = hasConfig(this.context.context, TStylingConfig.HasMapBindings); + const mapBindingsFlag = + this._isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; + const hasMaps = hasConfig(this._tNode, mapBindingsFlag); if (hasMaps) { activateStylingMapFeature(); } @@ -480,25 +486,34 @@ export class NodeStylingDebug implements DebugNodeStyling { // run the template bindings applyStylingViaContext( - this.context.context, null, mockElement, data, true, mapFn, sanitizer, false); + this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, false, + this._isClassBased); // and also the host bindings applyStylingViaContext( - this.context.context, null, mockElement, data, true, mapFn, sanitizer, true); + this.context.context, this._tNode, null, mockElement, data, true, mapFn, sanitizer, true, + this._isClassBased); } } -function buildConfig(context: TStylingContext) { - const hasMapBindings = hasConfig(context, TStylingConfig.HasMapBindings); - const hasPropBindings = hasConfig(context, TStylingConfig.HasPropBindings); - const hasCollisions = hasConfig(context, TStylingConfig.HasCollisions); - const hasTemplateBindings = hasConfig(context, TStylingConfig.HasTemplateBindings); - const hasHostBindings = hasConfig(context, TStylingConfig.HasHostBindings); +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(context, false); + const allowDirectStyling = _allowDirectStyling(tNode, isClassBased, false); return { hasMapBindings, // hasPropBindings, // diff --git a/packages/core/src/render3/util/styling_utils.ts b/packages/core/src/render3/util/styling_utils.ts index fd62e9f5c8..bc35b63967 100644 --- a/packages/core/src/render3/util/styling_utils.ts +++ b/packages/core/src/render3/util/styling_utils.ts @@ -5,8 +5,8 @@ * 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 {PropertyAliases, TNode, TNodeFlags} from '../interfaces/node'; -import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from '../interfaces/styling'; +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]'; @@ -44,17 +44,9 @@ export const DEFAULT_GUARD_MASK_VALUE = 0b1; export function allocTStylingContext( initialStyling: StylingMapArray | null, hasDirectives: boolean): TStylingContext { initialStyling = initialStyling || allocStylingMapArray(null); - let config = TStylingConfig.Initial; - if (hasDirectives) { - config |= TStylingConfig.HasDirectives; - } - if (initialStyling.length > StylingMapArrayIndex.ValuesStartPosition) { - config |= TStylingConfig.HasInitialStyling; - } return [ - config, // 1) config for the styling context - DEFAULT_TOTAL_SOURCES, // 2) total amount of styling sources (template, directives, etc...) - initialStyling, // 3) initial styling values + DEFAULT_TOTAL_SOURCES, // 1) total amount of styling sources (template, directives, etc...) + initialStyling, // 2) initial styling values ]; } @@ -62,12 +54,8 @@ export function allocStylingMapArray(value: {} | string | null): StylingMapArray return [value]; } -export function getConfig(context: TStylingContext) { - return context[TStylingContextIndex.ConfigPosition]; -} - -export function hasConfig(context: TStylingContext, flag: TStylingConfig) { - return (getConfig(context) & flag) !== 0; +export function hasConfig(tNode: TStylingNode, flag: TNodeFlags) { + return (tNode.flags & flag) !== 0; } /** @@ -81,36 +69,35 @@ export function hasConfig(context: TStylingContext, flag: TStylingConfig) { * 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(context: TStylingContext, firstUpdatePass: boolean): boolean { +export function allowDirectStyling( + tNode: TStylingNode, isClassBased: boolean, firstUpdatePass: boolean): boolean { let allow = false; - const config = getConfig(context); - const hasNoDirectives = (config & TStylingConfig.HasDirectives) === 0; // 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... - if (hasNoDirectives) { + 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 hasNoCollisions = (config & TStylingConfig.HasCollisions) === 0; - const hasOnlyMapsOrOnlyProps = - (config & TStylingConfig.HasPropAndMapBindings) !== TStylingConfig.HasPropAndMapBindings; - allow = hasNoCollisions && hasOnlyMapsOrOnlyProps; + 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 setConfig(context: TStylingContext, value: TStylingConfig): void { - context[TStylingContextIndex.ConfigPosition] = value; -} - -export function patchConfig(context: TStylingContext, flag: TStylingConfig): void { - context[TStylingContextIndex.ConfigPosition] |= flag; +export function patchConfig(tNode: TStylingNode, flag: TNodeFlags): void { + tNode.flags |= flag; } export function getProp(context: TStylingContext, index: number): string { @@ -173,9 +160,11 @@ export function getValue(data: LStylingData, bindingIndex: number): T|n return bindingIndex !== 0 ? data[bindingIndex] as T : null; } -export function getPropValuesStartPosition(context: TStylingContext) { +export function getPropValuesStartPosition( + context: TStylingContext, tNode: TStylingNode, isClassBased: boolean) { let startPosition = TStylingContextIndex.ValuesStartPosition; - if (hasConfig(context, TStylingConfig.HasMapBindings)) { + const flag = isClassBased ? TNodeFlags.hasClassMapBindings : TNodeFlags.hasStyleMapBindings; + if (hasConfig(tNode, flag)) { startPosition += TStylingContextIndex.BindingsStartOffset + getValuesCount(context); } return startPosition; @@ -247,11 +236,11 @@ export function getInitialStylingValue(context: TStylingContext | StylingMapArra return map && (map[StylingMapArrayIndex.RawValuePosition] as string | null) || ''; } -export function hasClassInput(tNode: TNode) { +export function hasClassInput(tNode: TStylingNode) { return (tNode.flags & TNodeFlags.hasClassInput) !== 0; } -export function hasStyleInput(tNode: TNode) { +export function hasStyleInput(tNode: TStylingNode) { return (tNode.flags & TNodeFlags.hasStyleInput) !== 0; } diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 6cd8042102..60337420fc 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -632,9 +632,6 @@ { "name": "getComponentViewByInstance" }, - { - "name": "getConfig" - }, { "name": "getConstant" }, @@ -932,6 +929,9 @@ { "name": "isForwardRef" }, + { + "name": "isHostStyling" + }, { "name": "isHostStylingActive" }, @@ -1067,6 +1067,9 @@ { "name": "patchConfig" }, + { + "name": "patchHostStylingFlag" + }, { "name": "readPatchedData" }, diff --git a/packages/core/test/render3/styling_next/styling_context_spec.ts b/packages/core/test/render3/styling_next/styling_context_spec.ts index a2e4cd4d1b..89e914cbca 100644 --- a/packages/core/test/render3/styling_next/styling_context_spec.ts +++ b/packages/core/test/render3/styling_next/styling_context_spec.ts @@ -5,11 +5,22 @@ * 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 {registerBinding} from '@angular/core/src/render3/styling/bindings'; +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); @@ -111,7 +122,9 @@ describe('styling context', () => { function makeContextWithDebug(isClassBased: boolean) { const ctx = allocTStylingContext(null, false); - return attachStylingDebugObject(ctx, isClassBased); + const tNode: TStylingNode = {flags: 0}; + (ctx as any).tNode = ctx; + return attachStylingDebugObject(ctx, tNode, isClassBased); } function buildGuardMask(...bindingIndices: number[]) { diff --git a/packages/core/test/render3/styling_next/styling_debug_spec.ts b/packages/core/test/render3/styling_next/styling_debug_spec.ts index 5c1497750a..75cdf5decf 100644 --- a/packages/core/test/render3/styling_next/styling_debug_spec.ts +++ b/packages/core/test/render3/styling_next/styling_debug_spec.ts @@ -5,6 +5,7 @@ * 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'; @@ -15,12 +16,14 @@ describe('styling debugging tools', () => { () => { if (isIE()) return; - const debug = makeContextWithDebug(false); - const context = debug.context; - const data: any[] = []; - const d = new NodeStylingDebug(context, data, false); + const values = makeContextWithDebug(false); + const context = values.context; + const tNode = values.tNode; - registerBinding(context, 0, 0, 'width', null); + 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', @@ -29,7 +32,7 @@ describe('styling debugging tools', () => { }, }); - registerBinding(context, 0, 0, 'width', '100px'); + registerBinding(context, tNode, 0, 0, 'width', '100px', false, false); expect(d.summary).toEqual({ width: { prop: 'width', @@ -41,7 +44,7 @@ describe('styling debugging tools', () => { const someBindingIndex1 = 1; data[someBindingIndex1] = '200px'; - registerBinding(context, 0, 0, 'width', someBindingIndex1); + registerBinding(context, tNode, 0, 0, 'width', someBindingIndex1, false, false); expect(d.summary).toEqual({ width: { prop: 'width', @@ -53,7 +56,7 @@ describe('styling debugging tools', () => { const someBindingIndex2 = 2; data[someBindingIndex2] = '500px'; - registerBinding(context, 0, 1, 'width', someBindingIndex2); + registerBinding(context, tNode, 0, 1, 'width', someBindingIndex2, false, false); expect(d.summary).toEqual({ width: { prop: 'width', @@ -66,8 +69,14 @@ describe('styling debugging tools', () => { }); function makeContextWithDebug(isClassBased: boolean) { - const ctx = allocTStylingContext(null, false); - return attachStylingDebugObject(ctx, isClassBased); + const context = allocTStylingContext(null, false); + const tNode = createTStylingNode(); + attachStylingDebugObject(context, tNode, isClassBased); + return {context, tNode}; +} + +function createTStylingNode(): TStylingNode { + return {flags: 0}; } function isIE() {