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
This commit is contained in:
Matias Niemelä 2019-11-01 15:53:50 -07:00 committed by atscott
parent e89c2dd8d0
commit 41560b47c4
11 changed files with 369 additions and 292 deletions

View File

@ -39,7 +39,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 265613,
"main-es2015": 268331,
"polyfills-es2015": 36808,
"5-es2015": 751
}

View File

@ -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),

View File

@ -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);
}

View File

@ -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. `<div [class]="x">`
* 2. `@HostBinding('class') x`
*/
hasClassMapBindings = 0x200,
/**
* Bit #11 - Whether or not there are any class-based prop bindings present.
*
* Examples include:
* 1. `<div [class.name]="x">`
* 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. `<div [class]="x">`
* 2. `<div [class.name]="x">`
*/
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: `<div [class.active]="x" dir-that-sets-active-class>`
* 2. map + prop: `<div [class]="x" [class.foo]>`
* 3. map + map: `<div [class]="x" dir-that-sets-class>`
*/
hasDuplicateClassBindings = 0x2000,
/**
* Bit #16 - Whether or not there are style-based map bindings present.
*
* Examples include:
* 1. `<div [style]="x">`
* 2. `@HostBinding('style') x`
*/
hasStyleMapBindings = 0x4000,
/**
* Bit #17 - Whether or not there are any style-based prop bindings present.
*
* Examples include:
* 1. `<div [style.prop]="x">`
* 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. `<div [style]="x">`
* 2. `<div [style.prop]="x">`
*/
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: `<div [style.width]="x" dir-that-sets-width>`
* 2. map + prop: `<div [style]="x" [style.prop]>`
* 3. map + map: `<div [style]="x" dir-that-sets-style>`
*/
hasDuplicateStyleBindings = 0x40000,
}
/**

View File

@ -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';
* // ...
* // </div>
* 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<number|string|number|boolean|null|StylingMapArray|{}> {
/** 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. `<div dir-one>`
* 2. `<div dir-one [dir-two]="x">`
* 3. `<comp>`
* 4. `<comp dir-one>`
*/
HasDirectives = 0b000001,
/**
* Whether or not there are prop-based bindings present.
*
* Examples include:
* 1. `<div [style.prop]="x">`
* 2. `<div [class.prop]="x">`
* 3. `@HostBinding('style.prop') x`
* 4. `@HostBinding('class.prop') x`
*/
HasPropBindings = 0b000010,
/**
* Whether or not there are map-based bindings present.
*
* Examples include:
* 1. `<div [style]="x">`
* 2. `<div [class]="x">`
* 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. `<div [style]="x" [style.prop]="y">`
* 2. `<div [class]="x" [style.prop]="y">`
* 3. `<div [style]="x" dir-that-sets-some-prop>`
* 4. `<div [class]="x" dir-that-sets-some-class>`
*/
HasPropAndMapBindings = HasPropBindings | HasMapBindings,
/**
* Whether or not there are two or more sources for a single property in the context.
*
* Examples include:
* 1. prop + prop: `<div [style.width]="x" dir-that-sets-width>`
* 2. map + prop: `<div [style]="x" [style.prop]>`
* 3. map + map: `<div [style]="x" dir-that-sets-style>`
*/
HasCollisions = 0b001000,
/**
* Whether or not the context contains initial styling values.
*
* Examples include:
* 1. `<div style="width:200px">`
* 2. `<div class="one two three">`
* 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. `<div [style]="x">`
* 2. `<div [style.width]="x">`
* 3. `<div [class]="x">`
* 4. `<div [class.name]="x">`
*/
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; }

View File

@ -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. `<div [style.width]="w">`) 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;

View File

@ -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, //

View File

@ -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<T = any>(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;
}

View File

@ -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"
},

View File

@ -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[]) {

View File

@ -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() {