From 931e603f8008fd523de6316965baea182a6e4d41 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 12 Oct 2018 15:02:54 -0700 Subject: [PATCH] refactor(ivy): revert LNode.data into LViewData[HOST] (#26424) PR Close #26424 --- .../core/src/core_render3_private_export.ts | 5 +- packages/core/src/render3/component.ts | 76 +++++-- packages/core/src/render3/component_ref.ts | 24 +-- .../core/src/render3/context_discovery.ts | 121 ++--------- packages/core/src/render3/discovery_utils.ts | 11 +- packages/core/src/render3/i18n.ts | 16 +- packages/core/src/render3/instructions.ts | 202 +++++------------- .../core/src/render3/interfaces/container.ts | 30 +-- .../core/src/render3/interfaces/context.ts | 59 +++++ packages/core/src/render3/interfaces/node.ts | 24 +-- .../core/src/render3/interfaces/styling.ts | 25 +-- packages/core/src/render3/interfaces/view.ts | 48 +++-- .../core/src/render3/node_manipulation.ts | 24 +-- .../styling/class_and_style_bindings.ts | 15 -- packages/core/src/render3/styling/util.ts | 75 ++++++- packages/core/src/render3/util.ts | 55 ++++- .../src/render3/view_engine_compatibility.ts | 8 +- .../bundle.golden_symbols.json | 35 +-- .../hello_world/bundle.golden_symbols.json | 17 +- .../bundling/todo/bundle.golden_symbols.json | 35 +-- .../todo_r2/bundle.golden_symbols.json | 34 +-- .../test/render3/change_detection_spec.ts | 74 ++++++- .../core/test/render3/instructions_spec.ts | 6 +- .../core/test/render3/integration_spec.ts | 61 ++++-- packages/core/test/render3/render_util.ts | 18 +- .../core/test/render3/styling/styling_spec.ts | 54 ++--- 26 files changed, 634 insertions(+), 518 deletions(-) create mode 100644 packages/core/src/render3/interfaces/context.ts diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 9cdf38a129..d63b818482 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -158,7 +158,6 @@ export { } from './sanitization/bypass'; export { - LContext as ɵLContext, getContext as ɵgetContext } from './render3/context_discovery'; @@ -168,6 +167,10 @@ export { PlayerHandler as ɵPlayerHandler, } from './render3/interfaces/player'; +export { + LContext as ɵLContext, +} from './render3/interfaces/context'; + export { addPlayer as ɵaddPlayer, getPlayers as ɵgetPlayers, diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 438d056c21..9bb2ec4d58 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -13,19 +13,22 @@ import {Injector} from '../di/injector'; import {Sanitizer} from '../sanitization/security'; import {assertComponentType, assertDefined} from './assert'; -import {getLElementFromComponent, readPatchedLViewData} from './context_discovery'; +import {getComponentViewByInstance} from './context_discovery'; import {getComponentDef} from './definition'; import {queueInitHooks, queueLifecycleHooks} from './hooks'; -import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, hostElement, leaveView, locateHostElement, prefillHostVars, setHostBindings} from './instructions'; +import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createNodeAtIndex, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getOrCreateTView, leaveView, locateHostElement, prefillHostVars, resetComponentState, setHostBindings} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; -import {LElementNode, TNodeFlags} from './interfaces/node'; +import {TNodeFlags, TNodeType} from './interfaces/node'; import {PlayerHandler} from './interfaces/player'; -import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; -import {CONTEXT, INJECTOR, LViewData, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view'; -import {getRootView, stringify} from './util'; +import {RElement, RNode, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; +import {CONTEXT, HEADER_OFFSET, HOST, INJECTOR, LViewData, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view'; +import {getRootView, readElementValue, readPatchedLViewData, stringify} from './util'; +// Root component will always have an element index of 0 and an injector size of 1 +const ROOT_EXPANDO_INSTRUCTIONS = [0, 1]; + /** Options that control how the component should be bootstrapped. */ export interface CreateComponentOptions { /** Which renderer factory to use. */ @@ -111,30 +114,29 @@ export function renderComponent( // The first index of the first selector is the tag name. const componentTag = componentDef.selectors ![0] ![0] as string; - const hostNode = locateHostElement(rendererFactory, opts.host || componentTag); + const hostRNode = locateHostElement(rendererFactory, opts.host || componentTag); const rootFlags = componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot : LViewFlags.CheckAlways | LViewFlags.IsRoot; const rootContext = createRootContext( opts.scheduler || requestAnimationFrame.bind(window), opts.playerHandler || null); + const renderer = rendererFactory.createRenderer(hostRNode, componentDef); const rootView: LViewData = createLViewData( - rendererFactory.createRenderer(hostNode, componentDef), - createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags); + renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags); rootView[INJECTOR] = opts.injector || null; const oldView = enterView(rootView, null); - let elementNode: LElementNode; let component: T; try { if (rendererFactory.begin) rendererFactory.begin(); - // Create element node at index 0 in data array - elementNode = hostElement(componentTag, hostNode, componentDef, sanitizer); + const componentView = + createRootComponentView(hostRNode, componentDef, rootView, renderer, sanitizer); component = createRootComponent( - elementNode, componentDef, rootView, rootContext, opts.hostFeatures || null); + hostRNode, componentView, componentDef, rootView, rootContext, opts.hostFeatures || null); executeInitAndContentHooks(); - detectChangesInternal(elementNode.data as LViewData, component); + detectChangesInternal(componentView, component); } finally { leaveView(oldView); if (rendererFactory.end) rendererFactory.end(); @@ -143,19 +145,55 @@ export function renderComponent( return component; } +/** + * Creates the root component view and the root component node. + * + * @param rNode Render host element. + * @param def ComponentDef + * @param rootView The parent view where the host node is stored + * @param renderer The current renderer + * @param sanitizer The sanitizer, if provided + * + * @returns Component view created + */ +export function createRootComponentView( + rNode: RElement | null, def: ComponentDef, rootView: LViewData, renderer: Renderer3, + sanitizer?: Sanitizer | null): LViewData { + resetComponentState(); + const tView = rootView[TVIEW]; + const componentView = createLViewData( + renderer, + getOrCreateTView( + def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery), + null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer); + const tNode = createNodeAtIndex(0, TNodeType.Element, rNode, null, null, componentView); + + if (tView.firstTemplatePass) { + tView.expandoInstructions = ROOT_EXPANDO_INSTRUCTIONS.slice(); + if (def.diPublic) def.diPublic(def); + tNode.flags = + rootView.length << TNodeFlags.DirectiveStartingIndexShift | TNodeFlags.isComponent; + } + + // Store component view at node index, with node as the HOST + componentView[HOST] = rootView[HEADER_OFFSET]; + return rootView[HEADER_OFFSET] = componentView; +} + + /** * Creates a root component and sets it up with features and host bindings. Shared by * renderComponent() and ViewContainerRef.createComponent(). */ export function createRootComponent( - elementNode: LElementNode, componentDef: ComponentDef, rootView: LViewData, - rootContext: RootContext, hostFeatures: HostFeature[] | null): any { + hostRNode: RNode | null, componentView: LViewData, componentDef: ComponentDef, + rootView: LViewData, rootContext: RootContext, hostFeatures: HostFeature[] | null): any { // Create directive instance with factory() and store at next index in viewData const component = - baseDirectiveCreate(rootView.length, componentDef.factory() as T, componentDef, elementNode); + baseDirectiveCreate(rootView.length, componentDef.factory() as T, componentDef, hostRNode); rootContext.components.push(component); - (elementNode.data as LViewData)[CONTEXT] = component; + componentView[CONTEXT] = component; hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef)); if (rootView[TVIEW].firstTemplatePass) prefillHostVars(componentDef.hostVars); @@ -217,7 +255,7 @@ function getRootContext(component: any): RootContext { * @param component Component for which the host element should be retrieved. */ export function getHostElement(component: T): HTMLElement { - return getLElementFromComponent(component).native as any; + return readElementValue(getComponentViewByInstance(component)).native as HTMLElement; } /** diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index b861fca7c8..198b013ec8 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -17,13 +17,14 @@ import {RendererFactory2} from '../render/api'; import {Type} from '../type'; import {assertComponentType, assertDefined} from './assert'; -import {LifecycleHooksFeature, createRootComponent, createRootContext} from './component'; +import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component'; import {getComponentDef} from './definition'; -import {adjustBlueprintForNewNode, createLViewData, createNodeAtIndex, createTView, elementCreate, enterView, getTNode, hostElement, locateHostElement, renderEmbeddedTemplate} from './instructions'; +import {adjustBlueprintForNewNode, createLViewData, createNodeAtIndex, createTView, elementCreate, enterView, locateHostElement, renderEmbeddedTemplate} from './instructions'; import {ComponentDef, RenderFlags} from './interfaces/definition'; -import {LElementNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; +import {TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {FLAGS, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view'; +import {getTNode} from './util'; import {createElementRef} from './view_engine_compatibility'; import {RootViewRef, ViewRef} from './view_ref'; @@ -109,7 +110,7 @@ export class ComponentFactory extends viewEngine_ComponentFactory { rendererFactory = domRendererFactory3; } - const hostNode = isInternalRootView ? + const hostRNode = isInternalRootView ? elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef)) : locateHostElement(rendererFactory, rootSelectorOrNode); @@ -122,24 +123,23 @@ export class ComponentFactory extends viewEngine_ComponentFactory { ngModule.injector.get(ROOT_CONTEXT) : createRootContext(requestAnimationFrame.bind(window)); + const renderer = rendererFactory.createRenderer(hostRNode, this.componentDef); // Create the root view. Uses empty TView and ContentTemplate. const rootView: LViewData = createLViewData( - rendererFactory.createRenderer(hostNode, this.componentDef), - createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags); + renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags); rootView[INJECTOR] = ngModule && ngModule.injector || null; // rootView is the parent when bootstrapping const oldView = enterView(rootView, null); let component: T; - let elementNode: LElementNode; let tElementNode: TElementNode; try { if (rendererFactory.begin) rendererFactory.begin(); - // Create element node at index 0 in data array - elementNode = hostElement(componentTag, hostNode, this.componentDef); - tElementNode = getTNode(0) as TElementNode; + const componentView = + createRootComponentView(hostRNode, this.componentDef, rootView, renderer); + tElementNode = getTNode(0, rootView) as TElementNode; // Transform the arrays of native nodes into a LNode structure that can be consumed by the // projection instruction. This is needed to support the reprojection of these nodes. @@ -165,10 +165,10 @@ export class ComponentFactory extends viewEngine_ComponentFactory { // executed here? // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref component = createRootComponent( - elementNode, this.componentDef, rootView, rootContext, [LifecycleHooksFeature]); + hostRNode, componentView, this.componentDef, rootView, rootContext, + [LifecycleHooksFeature]); // Execute the template in creation mode only, and then turn off the CreationMode flag - const componentView = elementNode.data as LViewData; renderEmbeddedTemplate(componentView, componentView[TVIEW], component, RenderFlags.Create); componentView[FLAGS] &= ~LViewFlags.CreationMode; } finally { diff --git a/packages/core/src/render3/context_discovery.ts b/packages/core/src/render3/context_discovery.ts index c0c72cf2cb..a39d738d95 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -8,59 +8,12 @@ import './ng_dev_mode'; import {assertEqual} from './assert'; -import {ACTIVE_INDEX, HOST_NATIVE, LContainer} from './interfaces/container'; +import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context'; import {LElementNode, TNode, TNodeFlags} from './interfaces/node'; import {RElement} from './interfaces/renderer'; -import {StylingContext, StylingIndex} from './interfaces/styling'; -import {CONTEXT, HEADER_OFFSET, LViewData, TVIEW} from './interfaces/view'; +import {CONTEXT, HEADER_OFFSET, HOST, LViewData, TVIEW} from './interfaces/view'; +import {getComponentViewByIndex, readElementValue, readPatchedData} from './util'; -/** - * This property will be monkey-patched on elements, components and directives - */ -export const MONKEY_PATCH_KEY_NAME = '__ngContext__'; - -/** - * The internal view context which is specific to a given DOM element, directive or - * component instance. Each value in here (besides the LViewData and element node details) - * can be present, null or undefined. If undefined then it implies the value has not been - * looked up yet, otherwise, if null, then a lookup was executed and nothing was found. - * - * Each value will get filled when the respective value is examined within the getContext - * function. The component, element and each directive instance will share the same instance - * of the context. - */ -export interface LContext { - /** - * The component's parent view data. - */ - lViewData: LViewData; - - /** - * The index instance of the node. - */ - nodeIndex: number; - - /** - * The instance of the DOM node that is attached to the lNode. - */ - native: RElement; - - /** - * The instance of the Component node. - */ - component: {}|null|undefined; - - /** - * The list of active directives that exist on this element. - */ - directives: any[]|null|undefined; - - /** - * The map of local references (local reference name => element or directive instance) that exist - * on this element. - */ - localRefs: {[key: string]: any}|null|undefined; -} /** Returns the matching `LContext` data for a given DOM node, directive or component instance. * @@ -187,29 +140,27 @@ function createLContext(lViewData: LViewData, lNodeIndex: number, native: REleme } /** - * A simplified lookup function for finding the LElementNode from a component instance. + * Takes a component instance and returns the view for that component. * - * This function exists for tree-shaking purposes to avoid having to pull in everything - * that `getContext` has in the event that an Angular application doesn't need to have - * any programmatic access to an element's context (only change detection uses this function). + * @param componentInstance + * @returns The component's view */ -export function getLElementFromComponent(componentInstance: {}): LElementNode { +export function getComponentViewByInstance(componentInstance: {}): LViewData { let lViewData = readPatchedData(componentInstance); - let lNode: LElementNode; + let view: LViewData; if (Array.isArray(lViewData)) { const lNodeIndex = findViaComponent(lViewData, componentInstance); - lNode = readElementValue(lViewData[lNodeIndex]); - const context = createLContext(lViewData, lNodeIndex, lNode.native); + view = getComponentViewByIndex(lNodeIndex, lViewData); + const context = createLContext(lViewData, lNodeIndex, (view[HOST] as LElementNode).native); context.component = componentInstance; attachPatchData(componentInstance, context); attachPatchData(context.native, context); } else { const context = lViewData as any as LContext; - lNode = readElementValue(context.lViewData[context.nodeIndex]); + view = getComponentViewByIndex(context.nodeIndex, context.lViewData); } - - return lNode; + return view; } /** @@ -220,22 +171,6 @@ export function attachPatchData(target: any, data: LViewData | LContext) { target[MONKEY_PATCH_KEY_NAME] = data; } -/** - * Returns the monkey-patch value data present on the target (which could be - * a component, directive or a DOM node). - */ -export function readPatchedData(target: any): LViewData|LContext|null { - return target[MONKEY_PATCH_KEY_NAME]; -} - -export function readPatchedLViewData(target: any): LViewData|null { - const value = readPatchedData(target); - if (value) { - return Array.isArray(value) ? value : (value as LContext).lViewData; - } - return null; -} - export function isComponentInstance(instance: any): boolean { return instance && instance.constructor && instance.constructor.ngComponentDef; } @@ -282,14 +217,14 @@ function findViaComponent(lViewData: LViewData, componentInstance: {}): number { if (componentIndices) { for (let i = 0; i < componentIndices.length; i++) { const elementComponentIndex = componentIndices[i]; - const lNodeData = readElementValue(lViewData[elementComponentIndex] !).data !; - if (lNodeData[CONTEXT] === componentInstance) { + const componentView = getComponentViewByIndex(elementComponentIndex, lViewData); + if (componentView[CONTEXT] === componentInstance) { return elementComponentIndex; } } } else { - const rootNode = lViewData[HEADER_OFFSET]; - const rootComponent = rootNode.data[CONTEXT]; + const rootComponentView = getComponentViewByIndex(HEADER_OFFSET, lViewData); + const rootComponent = rootComponentView[CONTEXT]; if (rootComponent === componentInstance) { // we are dealing with the root element here therefore we know that the // element is the very first element after the HEADER data in the lView @@ -391,27 +326,3 @@ function getDirectiveEndIndex(tNode: TNode, startIndex: number): number { const count = tNode.flags & TNodeFlags.DirectiveCountMask; return count ? (startIndex + count) : -1; } - -/** - * Takes the value of a slot in `LViewData` and returns the element node. - * - * Normally, element nodes are stored flat, but if the node has styles/classes on it, - * it might be wrapped in a styling context. Or if that node has a directive that injects - * ViewContainerRef, it may be wrapped in an LContainer. - * - * @param value The initial value in `LViewData` - */ -export function readElementValue(value: LElementNode | StylingContext | LContainer): LElementNode { - if (Array.isArray(value)) { - if (typeof value[ACTIVE_INDEX] === 'number') { - // This is an LContainer. It may also have a styling context. - value = value[HOST_NATIVE] as LElementNode | StylingContext; - return Array.isArray(value) ? value[StylingIndex.ElementPosition] ! : value; - } else { - // This is a StylingContext, which stores the element node at 0. - return value[StylingIndex.ElementPosition] as LElementNode; - } - } else { - return value; // Regular LNode is stored here - } -} diff --git a/packages/core/src/render3/discovery_utils.ts b/packages/core/src/render3/discovery_utils.ts index 4f8fceb4dc..bbba6fb7fd 100644 --- a/packages/core/src/render3/discovery_utils.ts +++ b/packages/core/src/render3/discovery_utils.ts @@ -8,10 +8,13 @@ import {Injector} from '../di/injector'; import {assertDefined} from './assert'; -import {LContext, discoverDirectives, discoverLocalRefs, getContext, isComponentInstance, readPatchedLViewData} from './context_discovery'; +import {discoverDirectives, discoverLocalRefs, getContext, isComponentInstance} from './context_discovery'; import {NodeInjector} from './di'; -import {LElementNode, TElementNode, TNode, TNodeFlags} from './interfaces/node'; +import {LContext} from './interfaces/context'; +import {TElementNode, TNode, TNodeFlags} from './interfaces/node'; import {CONTEXT, FLAGS, LViewData, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view'; +import {getComponentViewByIndex, readPatchedLViewData} from './util'; + /** * NOTE: The following functions might not be ideal for core usage in Angular... @@ -61,8 +64,8 @@ export function getHostComponent(target: {}): T|null { const context = loadContext(target); const tNode = context.lViewData[TVIEW].data[context.nodeIndex] as TNode; if (tNode.flags & TNodeFlags.isComponent) { - const lNode = context.lViewData[context.nodeIndex] as LElementNode; - return lNode.data ![CONTEXT] as any as T; + const componentView = getComponentViewByIndex(context.nodeIndex, context.lViewData); + return componentView[CONTEXT] as any as T; } return null; } diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 1fb6d688a5..dec84bd250 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -7,13 +7,14 @@ */ import {assertEqual, assertLessThan} from './assert'; -import {NO_CHANGE, _getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, getTNode, load, loadElement, resetComponentState} from './instructions'; +import {NO_CHANGE, _getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, load, loadElement, resetComponentState} from './instructions'; import {LContainer, NATIVE, RENDER_PARENT} from './interfaces/container'; import {LContainerNode, LNode, TElementNode, TNode, TNodeType} from './interfaces/node'; import {StylingContext} from './interfaces/styling'; import {BINDING_INDEX, HEADER_OFFSET, HOST_NODE, TVIEW} from './interfaces/view'; import {appendChild, createTextNode, removeChild} from './node_manipulation'; -import {getLNode, isLContainer, stringify} from './util'; +import {getNative, getTNode, isLContainer, stringify} from './util'; + /** @@ -272,8 +273,7 @@ function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode): } } - const native = getLNode(tNode, viewData).native; - appendChild(native, tNode, viewData); + appendChild(getNative(tNode, viewData), tNode, viewData); const slotValue = viewData[tNode.index]; if (tNode.type !== TNodeType.Container && isLContainer(slotValue)) { @@ -320,7 +320,7 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]): } const renderer = getRenderer(); - const startTNode = getTNode(startIndex); + const startTNode = getTNode(startIndex, viewData); let localParentTNode: TNode = startTNode.parent || viewData[HOST_NODE] !; let localPreviousTNode: TNode = localParentTNode; resetComponentState(); // We don't want to add to the tree with the wrong previous node @@ -329,7 +329,7 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]): const instruction = instructions[i] as number; switch (instruction & I18nInstructions.InstructionMask) { case I18nInstructions.Element: - const elementTNode = getTNode(instruction & I18nInstructions.IndexMask); + const elementTNode = getTNode(instruction & I18nInstructions.IndexMask, viewData); localPreviousTNode = appendI18nNode(elementTNode, localParentTNode, localPreviousTNode); localParentTNode = elementTNode; break; @@ -338,7 +338,7 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]): case I18nInstructions.Any: const nodeIndex = instruction & I18nInstructions.IndexMask; localPreviousTNode = - appendI18nNode(getTNode(nodeIndex), localParentTNode, localPreviousTNode); + appendI18nNode(getTNode(nodeIndex, viewData), localParentTNode, localPreviousTNode); break; case I18nInstructions.Text: if (ngDevMode) { @@ -365,7 +365,7 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]): } const removeIndex = instruction & I18nInstructions.IndexMask; const removedNode: LNode|LContainerNode = loadElement(removeIndex); - const removedTNode = getTNode(removeIndex); + const removedTNode = getTNode(removeIndex, viewData); removeChild(removedTNode, removedNode.native || null, viewData); const slotValue = load(removeIndex) as LNode | LContainer | StylingContext; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index da0436a01a..e0af757d90 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -13,23 +13,24 @@ import {Sanitizer} from '../sanitization/security'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert'; -import {attachPatchData, getLElementFromComponent, readElementValue, readPatchedLViewData} from './context_discovery'; +import {attachPatchData, getComponentViewByInstance} from './context_discovery'; import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; -import {ACTIVE_INDEX, HOST_NATIVE, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; +import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container'; import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {INJECTOR_SIZE} from './interfaces/injector'; -import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; +import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LTextNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; -import {StylingContext} from './interfaces/styling'; -import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; +import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; -import {appendChild, appendProjectedNode, createTextNode, findComponentView, getHostElementNode, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; +import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; -import {assertDataInRangeInternal, getLNode, getRootView, isContentQueryHost, isDifferent, isLContainer, loadElementInternal, loadInternal, stringify} from './util'; +import {createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; +import {getStylingContext} from './styling/util'; +import {assertDataInRangeInternal, getComponentViewByIndex, getNative, getRootView, getTNode, isComponent, isContentQueryHost, isDifferent, loadElementInternal, loadInternal, readPatchedLViewData, stringify} from './util'; + /** @@ -139,12 +140,6 @@ export function restoreView(viewToRestore: OpaqueViewState) { /** Used to set the parent property when nodes are created and track query results. */ let previousOrParentTNode: TNode; -export function getPreviousOrParentNode(): LNode|null { - return previousOrParentTNode == null || previousOrParentTNode === viewData[HOST_NODE] ? - getHostElementNode(viewData) : - getLNode(previousOrParentTNode, viewData); -} - export function getPreviousOrParentTNode(): TNode { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return previousOrParentTNode; @@ -252,9 +247,6 @@ export function getBindingRoot() { return bindingRootIndex; } -// Root component will always have an element index of 0 and an injector size of 1 -const ROOT_EXPANDO_INSTRUCTIONS = [0, 1]; - const enum BindingDirection { Input, Output, @@ -413,8 +405,8 @@ export function createLViewData( renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags, sanitizer?: Sanitizer | null): LViewData { const instance = tView.blueprint.slice() as LViewData; - instance[PARENT] = instance[DECLARATION_VIEW] = viewData; instance[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit; + instance[PARENT] = instance[DECLARATION_VIEW] = viewData; instance[CONTEXT] = context; instance[INJECTOR] = viewData ? viewData[INJECTOR] : null; instance[RENDERER] = renderer; @@ -422,17 +414,6 @@ export function createLViewData( return instance; } -/** - * Creation of LNode object is extracted to a separate function so we always create LNode object - * with the same shape - * (same properties assigned in the same order). - */ -export function createLNodeObject( - type: TNodeType, native: RText | RElement | RComment | null, state: any): LElementNode& - LTextNode&LViewNode&LContainerNode&LProjectionNode { - return {native: native as any, data: state}; -} - /** * A common way of creating the LNode to make sure that all of them have same shape to * keep the execution code monomorphic and fast. @@ -456,14 +437,14 @@ export function createNodeAtIndex( attrs: TAttributes | null, data: null): TContainerNode; export function createNodeAtIndex( index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null, - lProjection: null): TProjectionNode; + data: null): TProjectionNode; export function createNodeAtIndex( index: number, type: TNodeType.ElementContainer, native: RComment, name: null, attrs: TAttributes | null, data: null): TElementContainerNode; export function createNodeAtIndex( index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null, - attrs: TAttributes | null, state?: null | LViewData | LContainer): TElementNode&TViewNode& - TContainerNode&TElementContainerNode&TProjectionNode { + attrs: TAttributes | null, state?: null | LViewData): TElementNode&TViewNode&TContainerNode& + TElementContainerNode&TProjectionNode { const parent = isParent ? previousOrParentTNode : previousOrParentTNode && previousOrParentTNode.parent; @@ -473,7 +454,7 @@ export function createNodeAtIndex( const tParent = parentInSameView ? parent as TElementNode | TContainerNode : null; const isState = state != null; - const node = createLNodeObject(type, native, isState ? state as any : null); + const node = {native: native as any}; let tNode: TNode; if (index === -1 || type === TNodeType.View) { @@ -574,10 +555,10 @@ export function resetComponentState() { */ export function renderTemplate( hostNode: RElement, templateFn: ComponentTemplate, consts: number, vars: number, context: T, - providedRendererFactory: RendererFactory3, host: LElementNode | null, + providedRendererFactory: RendererFactory3, hostView: LViewData | null, directives?: DirectiveDefListOrFactory | null, pipes?: PipeDefListOrFactory | null, - sanitizer?: Sanitizer | null): LElementNode { - if (host == null) { + sanitizer?: Sanitizer | null): LViewData { + if (hostView == null) { resetComponentState(); rendererFactory = providedRendererFactory; renderer = providedRendererFactory.createRenderer(null, null); @@ -588,16 +569,13 @@ export function renderTemplate( const componentTView = getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null); - const componentLView = + hostView = createLViewData(renderer, componentTView, context, LViewFlags.CheckAlways, sanitizer); - createNodeAtIndex(0, TNodeType.Element, hostNode, null, null, componentLView); - host = loadElement(0); + createNodeAtIndex(0, TNodeType.Element, hostNode, null, null, hostView); } - const hostView = host.data !; - ngDevMode && assertDefined(hostView, 'Host node should have an LView defined in host.data.'); renderComponentOrTemplate(hostView, context, templateFn); - return host; + return hostView; } /** @@ -877,10 +855,6 @@ export function elementCreate(name: string, overriddenRenderer?: Renderer3): REl return native; } -function nativeNodeLocalRefExtractor(tNode: TNode, currentView: LViewData): RNode { - return getLNode(tNode, currentView).native; -} - /** * Creates directive instances and populates local refs. * @@ -889,8 +863,7 @@ function nativeNodeLocalRefExtractor(tNode: TNode, currentView: LViewData): RNod * @param localRefExtractor mapping function that extracts local ref value from LNode */ function createDirectivesAndLocals( - localRefs: string[] | null | undefined, - localRefExtractor: LocalRefExtractor = nativeNodeLocalRefExtractor) { + localRefs: string[] | null | undefined, localRefExtractor: LocalRefExtractor = getNative) { if (!bindingsEnabled) return; if (firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; @@ -1096,7 +1069,7 @@ function saveResolvedLocalsInData(localRefExtractor: LocalRefExtractor): void { * @param pipes Pipe defs that should be saved on TView * @returns TView */ -function getOrCreateTView( +export function getOrCreateTView( templateFn: ComponentTemplate, consts: number, vars: number, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, viewQuery: ComponentQuery| null): TView { @@ -1235,35 +1208,6 @@ export function locateHostElement( return rNode; } -/** - * Creates the host LNode. - * - * @param rNode Render host element. - * @param def ComponentDef - * - * @returns LElementNode created - */ -export function hostElement( - tag: string, rNode: RElement | null, def: ComponentDef, - sanitizer?: Sanitizer | null): LElementNode { - resetComponentState(); - const tNode = createNodeAtIndex( - 0, TNodeType.Element, rNode, null, null, - createLViewData( - renderer, - getOrCreateTView( - def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery), - null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); - - if (firstTemplatePass) { - tView.expandoInstructions = ROOT_EXPANDO_INSTRUCTIONS.slice(); - if (def.diPublic) def.diPublic(def); - tNode.flags = - viewData.length << TNodeFlags.DirectiveStartingIndexShift | TNodeFlags.isComponent; - } - return viewData[HEADER_OFFSET]; -} - /** * Adds an event listener to the current node. * @@ -1282,17 +1226,17 @@ export function listener( // add native event listener - applicable to elements only if (tNode.type === TNodeType.Element) { - const node = getPreviousOrParentNode() as LElementNode; + const native = getNative(previousOrParentTNode, viewData) as RElement; ngDevMode && ngDevMode.rendererAddEventListener++; // In order to match current behavior, native DOM event listeners must be added for all // events (including outputs). if (isProceduralRenderer(renderer)) { - const cleanupFn = renderer.listen(node.native, eventName, listenerFn); + const cleanupFn = renderer.listen(native, eventName, listenerFn); storeCleanupFn(viewData, cleanupFn); } else { const wrappedListener = wrapListenerWithPreventDefault(listenerFn); - node.native.addEventListener(eventName, wrappedListener, useCapture); + native.addEventListener(eventName, wrappedListener, useCapture); const cleanupInstances = getCleanup(viewData); cleanupInstances.push(wrappedListener); if (firstTemplatePass) { @@ -1421,7 +1365,7 @@ export function elementProperty( index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void { if (value === NO_CHANGE) return; const node = loadElement(index) as LElementNode | LContainerNode | LElementContainerNode; - const tNode = getTNode(index); + const tNode = getTNode(index, viewData); // if tNode.inputs is undefined, a listener has created outputs, but inputs haven't // yet been checked if (tNode && tNode.inputs === undefined) { @@ -1433,7 +1377,7 @@ export function elementProperty( let dataValue: PropertyAliasValue|undefined; if (inputData && (dataValue = inputData[propName])) { setInputsForProperty(dataValue, value); - if (tNode.type === TNodeType.Element) markDirtyIfOnPush(node as LElementNode); + if (isComponent(tNode)) markDirtyIfOnPush(index + HEADER_OFFSET); } else if (tNode.type === TNodeType.Element) { // It is assumed that the sanitizer is only added when the compiler determines that the property // is risky, so sanitization can be done without further checks. @@ -1583,7 +1527,7 @@ function generatePropertyAliases( */ export function elementClassProp( index: number, stylingIndex: number, value: T | NO_CHANGE): void { - updateElementClassProp(getStylingContext(index), stylingIndex, value ? true : false); + updateElementClassProp(getStylingContext(index, viewData), stylingIndex, value ? true : false); } /** @@ -1630,34 +1574,6 @@ export function elementStyling( } } -/** - * Retrieve the `StylingContext` at a given index. - * - * This method lazily creates the `StylingContext`. This is because in most cases - * we have styling without any bindings. Creating `StylingContext` eagerly would mean that - * every style declaration such as `
` would result `StyleContext` - * which would create unnecessary memory pressure. - * - * @param index Index of the style allocation. See: `elementStyling`. - */ -function getStylingContext(index: number): StylingContext { - let slotValue = viewData[index + HEADER_OFFSET]; - - if (isLContainer(slotValue)) { - const lContainer = slotValue; - slotValue = lContainer[HOST_NATIVE]; - if (!Array.isArray(slotValue)) { - return lContainer[HOST_NATIVE] = - allocStylingContext(slotValue, getTNode(index).stylingTemplate !); - } - } else if (!Array.isArray(slotValue)) { - // This is a regular ElementNode - return viewData[index + HEADER_OFFSET] = - allocStylingContext(slotValue, getTNode(index).stylingTemplate !); - } - - return slotValue as StylingContext; -} /** * Apply all styling values to the element which have been queued by any styling instructions. @@ -1674,7 +1590,7 @@ function getStylingContext(index: number): StylingContext { * index.) */ export function elementStylingApply(index: number): void { - renderElementStyles(getStylingContext(index), renderer); + renderElementStyles(getStylingContext(index, viewData), renderer); } /** @@ -1713,7 +1629,7 @@ export function elementStyleProp( valueToAdd = value as any as string; } } - updateElementStyleProp(getStylingContext(index), styleIndex, valueToAdd); + updateElementStyleProp(getStylingContext(index, viewData), styleIndex, valueToAdd); } /** @@ -1740,7 +1656,7 @@ export function elementStyleProp( export function elementStylingMap( index: number, classes: {[key: string]: any} | string | null, styles?: {[styleName: string]: any} | null): void { - updateStylingMap(getStylingContext(index), classes, styles); + updateStylingMap(getStylingContext(index, viewData), classes, styles); } ////////////////////////// @@ -1800,11 +1716,12 @@ export function textBinding(index: number, value: T | NO_CHANGE): void { */ export function directiveCreate( directiveDefIdx: number, directive: T, directiveDef: DirectiveDef| ComponentDef): T { - const hostNode = getLNode(previousOrParentTNode, viewData); - const instance = baseDirectiveCreate(directiveDefIdx, directive, directiveDef, hostNode); + const native = getNative(previousOrParentTNode, viewData); + const instance = baseDirectiveCreate(directiveDefIdx, directive, directiveDef, native); if ((directiveDef as ComponentDef).template) { - hostNode.data ![CONTEXT] = directive; + const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData); + componentView[CONTEXT] = directive; } if (firstTemplatePass) { @@ -1826,7 +1743,7 @@ export function directiveCreate( } function addComponentLogic(def: ComponentDef): void { - const hostNode = getLNode(previousOrParentTNode, viewData); + const native = getNative(previousOrParentTNode, viewData); const tView = getOrCreateTView( def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery); @@ -1836,13 +1753,15 @@ function addComponentLogic(def: ComponentDef): void { const componentView = addToViewTree( viewData, previousOrParentTNode.index as number, createLViewData( - rendererFactory.createRenderer(hostNode.native as RElement, def), tView, null, + rendererFactory.createRenderer(native as RElement, def), tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer())); - // We need to set the host node/data here because when the component LNode was created, - // we didn't yet know it was a component (just an element). - (hostNode as{data: LViewData}).data = componentView; - (componentView as LViewData)[HOST_NODE] = previousOrParentTNode as TElementNode; + componentView[HOST_NODE] = previousOrParentTNode as TElementNode; + + // Component view will always be created before any injected LContainers, + // so this is a regular LNode, wrap it with the component view + componentView[HOST] = viewData[previousOrParentTNode.index]; + viewData[previousOrParentTNode.index] = componentView; if (firstTemplatePass) { queueComponentIndexForCheck(); @@ -1859,15 +1778,15 @@ function addComponentLogic(def: ComponentDef): void { */ export function baseDirectiveCreate( index: number, directive: T, directiveDef: DirectiveDef| ComponentDef, - hostNode: LNode): T { + native: RNode | null): T { ngDevMode && assertEqual( viewData[BINDING_INDEX], tView.bindingStartIndex, 'directives should be created before any bindings'); ngDevMode && assertPreviousIsParent(); attachPatchData(directive, viewData); - if (hostNode) { - attachPatchData(hostNode.native, viewData); + if (native) { + attachPatchData(native, viewData); } viewData[index] = directive; @@ -1897,7 +1816,7 @@ export function baseDirectiveCreate( } if (directiveDef !.attributes != null && previousOrParentTNode.type == TNodeType.Element) { - setUpAttributes((hostNode as LElementNode).native, directiveDef !.attributes as string[]); + setUpAttributes(native as RElement, directiveDef !.attributes as string[]); } return directive; @@ -1990,12 +1909,12 @@ export function createLContainer( native: RComment, isForViewContainerRef?: boolean): LContainer { return [ isForViewContainerRef ? -1 : 0, // active index + [], // views currentView, // parent null, // next null, // queries hostLNode, // host native native, // native - [], // views getRenderParent(hostTNode, currentView) // renderParent ]; } @@ -2269,11 +2188,8 @@ export function embeddedViewEnd(): void { export function componentRefresh( adjustedElementIndex: number, parentFirstTemplatePass: boolean): void { ngDevMode && assertDataInRange(adjustedElementIndex); - const element = readElementValue(viewData[adjustedElementIndex]) as LElementNode; + const hostView = getComponentViewByIndex(adjustedElementIndex, viewData); ngDevMode && assertNodeType(tView.data[adjustedElementIndex] as TNode, TNodeType.Element); - ngDevMode && - assertDefined(element.data, `Component's host node should have an LViewData attached.`); - const hostView = element.data !; // Only attached CheckAlways components or attached, dirty OnPush components should be checked if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { @@ -2465,10 +2381,10 @@ export function addToViewTree( /////////////////////////////// /** If node is an OnPush component, marks its LViewData dirty. */ -export function markDirtyIfOnPush(node: LElementNode): void { - // Because data flows down the component tree, ancestors do not need to be marked dirty - if (node.data && !(node.data[FLAGS] & LViewFlags.CheckAlways)) { - node.data[FLAGS] |= LViewFlags.Dirty; +export function markDirtyIfOnPush(viewIndex: number): void { + const view = getComponentViewByIndex(viewIndex, viewData); + if (!(view[FLAGS] & LViewFlags.CheckAlways)) { + view[FLAGS] |= LViewFlags.Dirty; } } @@ -2576,10 +2492,7 @@ function tickRootContext(rootContext: RootContext) { * @param component The component which the change detection should be performed on. */ export function detectChanges(component: T): void { - const hostNode = getLElementFromComponent(component) !; - ngDevMode && - assertDefined(hostNode, 'Component host node should be attached to an LViewData instance.'); - detectChangesInternal(hostNode.data !, component); + detectChangesInternal(getComponentViewByInstance(component) !, component); } /** @@ -2673,8 +2586,7 @@ function updateViewQuery(viewQuery: ComponentQuery<{}>| null, component: T): */ export function markDirty(component: T) { ngDevMode && assertDefined(component, 'component'); - const elementNode = getLElementFromComponent(component) !; - markViewDirty(elementNode.data as LViewData); + markViewDirty(getComponentViewByInstance(component)); } /////////////////////////////// @@ -2888,10 +2800,6 @@ export function loadElement(index: number): LElementNode { return loadElementInternal(index, viewData); } -export function getTNode(index: number): TNode { - return tView.data[index + HEADER_OFFSET] as TNode; -} - /** Gets the current binding value. */ export function getBinding(bindingIndex: number): any { ngDevMode && assertDataInRange(viewData[bindingIndex]); diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 3690471c34..5e24ac2d3a 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -10,7 +10,8 @@ import {LContainerNode, LElementContainerNode, LElementNode} from './node'; import {LQueries} from './query'; import {RComment} from './renderer'; import {StylingContext} from './styling'; -import {LViewData, NEXT, PARENT, QUERIES} from './view'; +import {HOST, LViewData, NEXT, PARENT, QUERIES} from './view'; + /** * Below are constants for LContainer indices to help us look up LContainer members @@ -18,11 +19,10 @@ import {LViewData, NEXT, PARENT, QUERIES} from './view'; * Uglify will inline these when minifying so there shouldn't be a cost. */ export const ACTIVE_INDEX = 0; -// PARENT, NEXT, and QUERIES are indices 1, 2, and 3. +export const VIEWS = 1; +// PARENT, NEXT, QUERIES, and HOST are indices 2, 3, 4, and 5. // As we already have these constants in LViewData, we don't need to re-create them. -export const HOST_NATIVE = 4; -export const NATIVE = 5; -export const VIEWS = 6; +export const NATIVE = 6; export const RENDER_PARENT = 7; /** @@ -43,6 +43,15 @@ export interface LContainer extends Array { */ [ACTIVE_INDEX]: number; + /** + * A list of the container's currently active child views. Views will be inserted + * here as they are added and spliced from here when they are removed. We need + * to keep a record of current views so we know which views are already in the DOM + * (and don't need to be re-added) and so we can remove views from the DOM when they + * are no longer required. + */ + [VIEWS]: LViewData[]; + /** * Access to the parent view is necessary so we can propagate back * up from inside a container to parent[NEXT]. @@ -63,20 +72,11 @@ export interface LContainer extends Array { /** The host node of this LContainer. */ // TODO: Should contain just the native element once LNode is removed. - [HOST_NATIVE]: LElementNode|LContainerNode|LElementContainerNode|StylingContext; + [HOST]: LElementNode|LContainerNode|LElementContainerNode|StylingContext|LViewData; /** The comment element that serves as an anchor for this LContainer. */ [NATIVE]: RComment; - /** - * A list of the container's currently active child views. Views will be inserted - * here as they are added and spliced from here when they are removed. We need - * to keep a record of current views so we know which views are already in the DOM - * (and don't need to be re-added) and so we can remove views from the DOM when they - * are no longer required. - */ - [VIEWS]: LViewData[]; - /** * Parent Element which will contain the location where all of the Views will be * inserted into to. diff --git a/packages/core/src/render3/interfaces/context.ts b/packages/core/src/render3/interfaces/context.ts new file mode 100644 index 0000000000..7f896ddafe --- /dev/null +++ b/packages/core/src/render3/interfaces/context.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + + +import {RElement} from './renderer'; +import {LViewData} from './view'; + +/** + * This property will be monkey-patched on elements, components and directives + */ +export const MONKEY_PATCH_KEY_NAME = '__ngContext__'; + +/** + * The internal view context which is specific to a given DOM element, directive or + * component instance. Each value in here (besides the LViewData and element node details) + * can be present, null or undefined. If undefined then it implies the value has not been + * looked up yet, otherwise, if null, then a lookup was executed and nothing was found. + * + * Each value will get filled when the respective value is examined within the getContext + * function. The component, element and each directive instance will share the same instance + * of the context. + */ +export interface LContext { + /** + * The component's parent view data. + */ + lViewData: LViewData; + + /** + * The index instance of the node. + */ + nodeIndex: number; + + /** + * The instance of the DOM node that is attached to the lNode. + */ + native: RElement; + + /** + * The instance of the Component node. + */ + component: {}|null|undefined; + + /** + * The list of active directives that exist on this element. + */ + directives: any[]|null|undefined; + + /** + * The map of local references (local reference name => element or directive instance) that exist + * on this element. + */ + localRefs: {[key: string]: any}|null|undefined; +} diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 49a0c94a09..258c570cd5 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -69,14 +69,6 @@ export interface LNode { * - retrieve the sibling elements of text nodes whose creation / insertion has been delayed */ readonly native: RComment|RElement|RText|null; - - /** - * If regular LElementNode, LTextNode, LContainerNode, and LProjectionNode then `data` will be - * null. - * If LElementNode with component, then `data` contains LViewData. - * If LViewNode, then `data` contains the LViewData. - */ - readonly data: LViewData|null; } @@ -84,30 +76,22 @@ export interface LNode { export interface LElementNode extends LNode { /** The DOM element associated with this node. */ readonly native: RElement; - - /** If Component then data has LView (light DOM) */ - readonly data: LViewData|null; } /** LNode representing . */ export interface LElementContainerNode extends LNode { /** The DOM comment associated with this node. */ readonly native: RComment; - readonly data: null; } /** LNode representing a #text node. */ export interface LTextNode extends LNode { /** The text node associated with this node. */ native: RText; - readonly data: null; } /** Abstract node which contains root nodes of a view. */ -export interface LViewNode extends LNode { - readonly native: null; - readonly data: LViewData; -} +export interface LViewNode extends LNode { readonly native: null; } /** Abstract node container which contains other views. */ export interface LContainerNode extends LNode { @@ -119,14 +103,10 @@ export interface LContainerNode extends LNode { * until the parent view is processed. */ native: RComment; - readonly data: null; } -export interface LProjectionNode extends LNode { - readonly native: null; - readonly data: null; -} +export interface LProjectionNode extends LNode { readonly native: null; } /** * A set of marker values to be used in the attributes arrays. Those markers indicate that some diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index 3b66e72731..8e8403b04d 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -121,11 +121,6 @@ import {PlayerContext} from './player'; export interface StylingContext extends Array { - /** - * Location of element that is used as a target for this context. - */ - [StylingIndex.ElementPosition]: LElementNode|null; - /** * Location of animation context (which contains the active players) for this element styling * context. @@ -156,6 +151,11 @@ export interface StylingContext extends */ [StylingIndex.ClassOffsetPosition]: number; + /** + * Location of element that is used as a target for this context. + */ + [StylingIndex.ElementPosition]: LElementNode|null; + /** * The last class value that was interpreted by elementStylingMap. This is cached * So that the algorithm can exit early incase the value has not changed. @@ -201,17 +201,18 @@ export const enum StylingFlags { /** Used as numeric pointer values to determine what cells to update in the `StylingContext` */ export const enum StylingIndex { // Position of where the initial styles are stored in the styling context - ElementPosition = 0, - // Position of where the initial styles are stored in the styling context - PlayerContext = 1, + PlayerContext = 0, // Position of where the style sanitizer is stored within the styling context - StyleSanitizerPosition = 2, + StyleSanitizerPosition = 1, // Position of where the initial styles are stored in the styling context - InitialStylesPosition = 3, + InitialStylesPosition = 2, // Index of location where the start of single properties are stored. (`updateStyleProp`) - MasterFlagPosition = 4, + MasterFlagPosition = 3, // Index of location where the class index offset value is located - ClassOffsetPosition = 5, + ClassOffsetPosition = 4, + // Position of where the initial styles are stored in the styling context + // This index must align with HOST, see interfaces/view.ts + ElementPosition = 5, // Position of where the last string-based CSS class value was stored PreviousMultiClassValue = 6, // Position of where the last string-based CSS class value was stored diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index bb3404f373..23c45e74e3 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -13,32 +13,34 @@ import {PlayerHandler} from '../interfaces/player'; import {LContainer} from './container'; import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition'; -import {TElementNode, TNode, TViewNode} from './node'; +import {LContainerNode, LElementContainerNode, LElementNode, TElementNode, TNode, TViewNode} from './node'; import {LQueries} from './query'; import {Renderer3} from './renderer'; +import {StylingContext} from './styling'; /** Size of LViewData's header. Necessary to adjust for it when setting slots. */ -export const HEADER_OFFSET = 16; +export const HEADER_OFFSET = 17; // Below are constants for LViewData indices to help us look up LViewData members // without having to remember the specific indices. // Uglify will inline these when minifying so there shouldn't be a cost. export const TVIEW = 0; -export const PARENT = 1; -export const NEXT = 2; -export const QUERIES = 3; -export const FLAGS = 4; -export const HOST_NODE = 5; -export const BINDING_INDEX = 6; -export const CLEANUP = 7; -export const CONTEXT = 8; -export const INJECTOR = 9; -export const RENDERER = 10; -export const SANITIZER = 11; -export const TAIL = 12; -export const CONTAINER_INDEX = 13; -export const CONTENT_QUERIES = 14; -export const DECLARATION_VIEW = 15; +export const FLAGS = 1; +export const PARENT = 2; +export const NEXT = 3; +export const QUERIES = 4; +export const HOST = 5; +export const HOST_NODE = 6; +export const BINDING_INDEX = 7; +export const CLEANUP = 8; +export const CONTEXT = 9; +export const INJECTOR = 10; +export const RENDERER = 11; +export const SANITIZER = 12; +export const TAIL = 13; +export const CONTAINER_INDEX = 14; +export const CONTENT_QUERIES = 15; +export const DECLARATION_VIEW = 16; // This interface replaces the real LViewData interface if it is an arg or a // return value of a public instruction. This ensures we don't need to expose @@ -66,6 +68,9 @@ export interface LViewData extends Array { */ [TVIEW]: TView; + /** Flags for this view. See LViewFlags for more info. */ + [FLAGS]: LViewFlags; + /** * The parent view is needed when we exit the view and must restore the previous * `LViewData`. Without this, the render method would have to keep a stack of @@ -90,8 +95,13 @@ export interface LViewData extends Array { /** Queries active for this view - nodes from a view are reported to those queries. */ [QUERIES]: LQueries|null; - /** Flags for this view. See LViewFlags for more info. */ - [FLAGS]: LViewFlags; + /** + * The host node for this LViewData instance, if this is a component view. + * + * If this is an embedded view, HOST will be null. + */ + // TODO: should store native elements directly when we remove LNode + [HOST]: LElementNode|LContainerNode|LElementContainerNode|StylingContext|null; /** * Pointer to the `TViewNode` or `TElementNode` which represents the root of the view. diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 5bde9d2dc0..d53141a571 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -7,16 +7,15 @@ */ import {assertDefined} from './assert'; -import {attachPatchData, readElementValue} from './context_discovery'; +import {attachPatchData} from './context_discovery'; import {callHooks} from './hooks'; -import {HOST_NATIVE, LContainer, NATIVE, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; -import {LContainerNode, LElementContainerNode, LElementNode, LTextNode, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; +import {LContainer, NATIVE, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; +import {LContainerNode, LElementContainerNode, LElementNode, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; -import {StylingIndex} from './interfaces/styling'; import {CLEANUP, CONTAINER_INDEX, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeType} from './node_assert'; -import {getLNode, isLContainer, stringify} from './util'; +import {getLNode, getNative, isLContainer, readElementValue, stringify} from './util'; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; @@ -104,8 +103,7 @@ function walkTNodeTree( let nextTNode: TNode|null = null; const parent = renderParentNode ? renderParentNode.native : null; if (tNode.type === TNodeType.Element) { - const elementNode = getLNode(tNode, currentView); - executeNodeAction(action, renderer, parent, elementNode.native !, beforeNode); + executeNodeAction(action, renderer, parent, getNative(tNode, currentView), beforeNode); const nodeOrContainer = currentView[tNode.index]; if (isLContainer(nodeOrContainer)) { // This element has an LContainer, and its comment needs to be handled @@ -397,13 +395,7 @@ export function removeView( /** Gets the child of the given LViewData */ export function getLViewChild(viewData: LViewData): LViewData|LContainer|null { const childIndex = viewData[TVIEW].childIndex; - if (childIndex === -1) return null; - - const value: LElementNode|LContainerNode|LContainer = viewData[childIndex]; - - // If it's an array, it's an LContainer. Otherwise, it's a component node, so LViewData - // is stored in data. - return Array.isArray(value) ? value : value.data; + return childIndex === -1 ? null : viewData[childIndex]; } /** @@ -668,7 +660,7 @@ export function getBeforeNodeForView(index: number, views: LViewData[], containe if (index + 1 < views.length) { const view = views[index + 1] as LViewData; const viewTNode = view[HOST_NODE] as TViewNode; - return viewTNode.child ? getLNode(viewTNode.child, view).native : containerNative; + return viewTNode.child ? getNative(viewTNode.child, view) : containerNative; } else { return containerNative; } @@ -706,7 +698,7 @@ export function removeChild(tNode: TNode, child: RNode | null, currentView: LVie export function appendProjectedNode( projectedTNode: TNode, tProjectionNode: TNode, currentView: LViewData, projectionView: LViewData): void { - const native = getLNode(projectedTNode, projectionView).native; + const native = getNative(projectedTNode, projectionView); appendChild(native, tProjectionNode, currentView); // the projected contents are processed while in the shadow view (which is the currentView) diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts index 9bdf68c6b8..18903f29a3 100644 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ b/packages/core/src/render3/styling/class_and_style_bindings.ts @@ -14,21 +14,6 @@ import {InitialStyles, StylingContext, StylingFlags, StylingIndex} from '../inte import {EMPTY_ARR, EMPTY_OBJ, createEmptyStylingContext} from './util'; - -/** - * Used clone a copy of a pre-computed template of a styling context. - * - * A pre-computed template is designed to be computed once for a given element - * (instructions.ts has logic for caching this). - */ -export function allocStylingContext( - lElement: LElementNode | null, templateStyleContext: StylingContext): StylingContext { - // each instance gets a copy - const context = templateStyleContext.slice() as any as StylingContext; - context[StylingIndex.ElementPosition] = lElement; - return context; -} - /** * Creates a styling context template where styling information is stored. * Any styles that are later referenced using `updateStyleProp` must be diff --git a/packages/core/src/render3/styling/util.ts b/packages/core/src/render3/styling/util.ts index d07896b16c..eae16f10f6 100644 --- a/packages/core/src/render3/styling/util.ts +++ b/packages/core/src/render3/styling/util.ts @@ -5,11 +5,16 @@ * 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 {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; -import {LContext, getContext} from '../context_discovery'; +import {getContext} from '../context_discovery'; +import {ACTIVE_INDEX, LContainer} from '../interfaces/container'; +import {LContext} from '../interfaces/context'; import {LElementNode} from '../interfaces/node'; import {PlayerContext} from '../interfaces/player'; import {InitialStyles, StylingContext, StylingIndex} from '../interfaces/styling'; +import {FLAGS, HEADER_OFFSET, HOST, LViewData} from '../interfaces/view'; +import {getTNode} from '../util'; export const EMPTY_ARR: any[] = []; export const EMPTY_OBJ: {[key: string]: any} = {}; @@ -18,10 +23,70 @@ export function createEmptyStylingContext( element?: LElementNode | null, sanitizer?: StyleSanitizeFn | null, initialStylingValues?: InitialStyles): StylingContext { return [ - element || null, null, sanitizer || null, initialStylingValues || [null], 0, 0, null, null + null, // PlayerContext + sanitizer || null, // StyleSanitizer + initialStylingValues || [null], // InitialStyles + 0, // MasterFlags + 0, // ClassOffset + element || null, // Element + null, // PreviousMultiClassValue + null // PreviousMultiStyleValue ]; } +/** + * Used clone a copy of a pre-computed template of a styling context. + * + * A pre-computed template is designed to be computed once for a given element + * (instructions.ts has logic for caching this). + */ +export function allocStylingContext( + lElement: LElementNode | null, templateStyleContext: StylingContext): StylingContext { + // each instance gets a copy + const context = templateStyleContext.slice() as any as StylingContext; + context[StylingIndex.ElementPosition] = lElement; + return context; +} + +/** + * Retrieve the `StylingContext` at a given index. + * + * This method lazily creates the `StylingContext`. This is because in most cases + * we have styling without any bindings. Creating `StylingContext` eagerly would mean that + * every style declaration such as `
` would result `StyleContext` + * which would create unnecessary memory pressure. + * + * @param index Index of the style allocation. See: `elementStyling`. + * @param viewData The view to search for the styling context + */ +export function getStylingContext(index: number, viewData: LViewData): StylingContext { + let storageIndex = index + HEADER_OFFSET; + let slotValue: LContainer|LViewData|StylingContext|LElementNode = viewData[storageIndex]; + let wrapper: LContainer|LViewData|StylingContext = viewData; + + while (Array.isArray(slotValue)) { + wrapper = slotValue; + slotValue = slotValue[HOST] as LViewData | StylingContext | LElementNode; + } + + if (isStylingContext(wrapper)) { + return wrapper as StylingContext; + } else { + // This is an LViewData or an LContainer + const stylingTemplate = getTNode(index, viewData).stylingTemplate; + + if (wrapper !== viewData) storageIndex = HOST; + return wrapper[storageIndex] = stylingTemplate ? + allocStylingContext(slotValue, stylingTemplate) : + createEmptyStylingContext(slotValue); + } +} + +function isStylingContext(value: LViewData | LContainer | StylingContext) { + // Not an LViewData or an LContainer + return typeof value[FLAGS] !== 'number' && typeof value[ACTIVE_INDEX] !== 'number'; +} + export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext { context = context || getContext(target) !; if (ngDevMode && !context) { @@ -30,11 +95,7 @@ export function getOrCreatePlayerContext(target: {}, context?: LContext | null): } const {lViewData, nodeIndex} = context; - const value = lViewData[nodeIndex]; - let stylingContext = value as StylingContext; - if (!Array.isArray(value)) { - stylingContext = lViewData[nodeIndex] = createEmptyStylingContext(value as LElementNode); - } + const stylingContext = getStylingContext(nodeIndex - HEADER_OFFSET, lViewData); return stylingContext[StylingIndex.PlayerContext] || allocPlayerContext(stylingContext); } diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts index fa90a0b0a3..f1873c246f 100644 --- a/packages/core/src/render3/util.ts +++ b/packages/core/src/render3/util.ts @@ -9,11 +9,12 @@ import {devModeEqual} from '../change_detection/change_detection_util'; import {assertDefined, assertLessThan} from './assert'; -import {readElementValue, readPatchedLViewData} from './context_discovery'; import {ACTIVE_INDEX, LContainer} from './interfaces/container'; +import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context'; import {LContainerNode, LElementContainerNode, LElementNode, LNode, TNode, TNodeFlags} from './interfaces/node'; +import {RComment, RElement, RText} from './interfaces/renderer'; import {StylingContext} from './interfaces/styling'; -import {CONTEXT, FLAGS, HEADER_OFFSET, LViewData, LViewFlags, PARENT, RootContext, TData, TVIEW} from './interfaces/view'; +import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, LViewData, LViewFlags, PARENT, RootContext, TData, TVIEW, TView} from './interfaces/view'; /** @@ -92,11 +93,45 @@ export function loadElementInternal(index: number, arr: LViewData): LElementNode return readElementValue(value); } +/** + * Takes the value of a slot in `LViewData` and returns the element node. + * + * Normally, element nodes are stored flat, but if the node has styles/classes on it, + * it might be wrapped in a styling context. Or if that node has a directive that injects + * ViewContainerRef, it may be wrapped in an LContainer. Or if that node is a component, + * it will be wrapped in LViewData. It could even have all three, so we keep looping + * until we find something that isn't an array. + * + * @param value The initial value in `LViewData` + */ +export function readElementValue(value: LElementNode | StylingContext | LContainer | LViewData): + LElementNode { + while (Array.isArray(value)) { + value = value[HOST] as any; + } + return value; +} + +export function getNative(tNode: TNode, hostView: LViewData): RElement|RText|RComment { + return getLNode(tNode, hostView).native; +} + +// TODO(kara): remove when removing LNode.native export function getLNode(tNode: TNode, hostView: LViewData): LElementNode|LContainerNode| LElementContainerNode { return readElementValue(hostView[tNode.index]); } +export function getTNode(index: number, view: LViewData): TNode { + return view[TVIEW].data[index + HEADER_OFFSET] as TNode; +} + +export function getComponentViewByIndex(nodeIndex: number, hostView: LViewData): LViewData { + // Could be an LViewData or an LContainer. If LContainer, unwrap to find LViewData. + const slotValue = hostView[nodeIndex]; + return slotValue.length >= HEADER_OFFSET ? slotValue : slotValue[HOST]; +} + export function isContentQueryHost(tNode: TNode): boolean { return (tNode.flags & TNodeFlags.hasContentQuery) !== 0; } @@ -128,3 +163,19 @@ export function getRootView(target: LViewData | {}): LViewData { export function getRootContext(viewOrComponent: LViewData | {}): RootContext { return getRootView(viewOrComponent)[CONTEXT] as RootContext; } + +/** + * Returns the monkey-patch value data present on the target (which could be + * a component, directive or a DOM node). + */ +export function readPatchedData(target: any): LViewData|LContext|null { + return target[MONKEY_PATCH_KEY_NAME]; +} + +export function readPatchedLViewData(target: any): LViewData|null { + const value = readPatchedData(target); + if (value) { + return Array.isArray(value) ? value : (value as LContext).lViewData; + } + return null; +} diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index c772dcb28c..5f191fe73d 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -28,7 +28,7 @@ import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/ import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getRenderParent, insertView, removeView} from './node_manipulation'; -import {getLNode, isComponent, isLContainer} from './util'; +import {getComponentViewByIndex, getNative, isComponent, isLContainer} from './util'; import {ViewRef} from './view_ref'; @@ -60,7 +60,7 @@ export function createElementRef( // TODO: Fix class name, should be ElementRef, but there appears to be a rollup bug R3ElementRef = class ElementRef_ extends ElementRefToken {}; } - return new R3ElementRef(getLNode(tNode, view).native); + return new R3ElementRef(getNative(tNode, view)); } let R3TemplateRef: { @@ -325,7 +325,7 @@ export function createViewRef( hostTNode: TNode, hostView: LViewData, context: any): ViewEngine_ChangeDetectorRef { if (isComponent(hostTNode)) { const componentIndex = hostTNode.flags >> TNodeFlags.DirectiveStartingIndexShift; - const componentView = getLNode(hostTNode, hostView).data as LViewData; + const componentView = getComponentViewByIndex(hostTNode.index, hostView); return new ViewRef(componentView, context, componentIndex); } else if (hostTNode.type === TNodeType.Element) { const hostComponentView = findComponentView(hostView); @@ -346,4 +346,4 @@ function getOrCreateRenderer2(view: LViewData): Renderer2 { /** Returns a Renderer2 (or throws when application was bootstrapped with Renderer3) */ export function injectRenderer2(): Renderer2 { return getOrCreateRenderer2(_getViewData()); -} \ No newline at end of file +} diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 706ea3682a..e08f6272aa 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -69,7 +69,7 @@ "name": "HEADER_OFFSET" }, { - "name": "HOST_NATIVE" + "name": "HOST" }, { "name": "HOST_NODE" @@ -374,9 +374,6 @@ { "name": "createLContext" }, - { - "name": "createLNodeObject" - }, { "name": "createLViewData" }, @@ -389,6 +386,9 @@ { "name": "createRootComponent" }, + { + "name": "createRootComponentView" + }, { "name": "createRootContext" }, @@ -551,6 +551,12 @@ { "name": "getComponentDef" }, + { + "name": "getComponentViewByIndex" + }, + { + "name": "getComponentViewByInstance" + }, { "name": "getContainerRenderParent" }, @@ -590,9 +596,6 @@ { "name": "getLContainer" }, - { - "name": "getLElementFromComponent" - }, { "name": "getLNode" }, @@ -608,6 +611,9 @@ { "name": "getMultiStartIndex" }, + { + "name": "getNative" + }, { "name": "getOrCreateInjectable" }, @@ -644,9 +650,6 @@ { "name": "getPreviousIndex" }, - { - "name": "getPreviousOrParentNode" - }, { "name": "getPreviousOrParentTNode" }, @@ -698,9 +701,6 @@ { "name": "hasValueChanged" }, - { - "name": "hostElement" - }, { "name": "inject" }, @@ -734,6 +734,9 @@ { "name": "isClassBased" }, + { + "name": "isComponent" + }, { "name": "isComponentInstance" }, @@ -782,6 +785,9 @@ { "name": "isSanitizable" }, + { + "name": "isStylingContext" + }, { "name": "iterateListLike" }, @@ -824,9 +830,6 @@ { "name": "nativeInsertBefore" }, - { - "name": "nativeNodeLocalRefExtractor" - }, { "name": "nextContext" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index a128452077..81c072ab40 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -39,7 +39,7 @@ "name": "HEADER_OFFSET" }, { - "name": "HOST_NATIVE" + "name": "HOST" }, { "name": "HOST_NODE" @@ -155,9 +155,6 @@ { "name": "componentRefresh" }, - { - "name": "createLNodeObject" - }, { "name": "createLViewData" }, @@ -167,6 +164,9 @@ { "name": "createRootComponent" }, + { + "name": "createRootComponentView" + }, { "name": "createRootContext" }, @@ -230,6 +230,9 @@ { "name": "getComponentDef" }, + { + "name": "getComponentViewByIndex" + }, { "name": "getContainerRenderParent" }, @@ -254,6 +257,9 @@ { "name": "getLViewChild" }, + { + "name": "getNative" + }, { "name": "getOrCreateNodeInjector" }, @@ -284,9 +290,6 @@ { "name": "getRenderParent" }, - { - "name": "hostElement" - }, { "name": "invertObject" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 4685600bee..4a124503bb 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -54,7 +54,7 @@ "name": "HEADER_OFFSET" }, { - "name": "HOST_NATIVE" + "name": "HOST" }, { "name": "HOST_NODE" @@ -437,9 +437,6 @@ { "name": "createLContext" }, - { - "name": "createLNodeObject" - }, { "name": "createLViewData" }, @@ -452,6 +449,9 @@ { "name": "createRootComponent" }, + { + "name": "createRootComponentView" + }, { "name": "createRootContext" }, @@ -602,6 +602,12 @@ { "name": "getComponentDef" }, + { + "name": "getComponentViewByIndex" + }, + { + "name": "getComponentViewByInstance" + }, { "name": "getContainerRenderParent" }, @@ -635,9 +641,6 @@ { "name": "getLContainer" }, - { - "name": "getLElementFromComponent" - }, { "name": "getLNode" }, @@ -650,6 +653,9 @@ { "name": "getMultiStartIndex" }, + { + "name": "getNative" + }, { "name": "getOrCreateInjectable" }, @@ -683,9 +689,6 @@ { "name": "getPreviousIndex" }, - { - "name": "getPreviousOrParentNode" - }, { "name": "getPreviousOrParentTNode" }, @@ -731,9 +734,6 @@ { "name": "hasValueChanged" }, - { - "name": "hostElement" - }, { "name": "inject" }, @@ -761,6 +761,9 @@ { "name": "invertObject" }, + { + "name": "isComponent" + }, { "name": "isContentQueryHost" }, @@ -800,6 +803,9 @@ { "name": "isProceduralRenderer" }, + { + "name": "isStylingContext" + }, { "name": "iterateListLike" }, @@ -845,9 +851,6 @@ { "name": "nativeInsertBefore" }, - { - "name": "nativeNodeLocalRefExtractor" - }, { "name": "nextContext" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 883ddd126f..b9003353db 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -321,10 +321,10 @@ "name": "HEADER_OFFSET" }, { - "name": "HOST_ATTR$1" + "name": "HOST" }, { - "name": "HOST_NATIVE" + "name": "HOST_ATTR$1" }, { "name": "HOST_NODE" @@ -1319,9 +1319,6 @@ { "name": "createLContext" }, - { - "name": "createLNodeObject" - }, { "name": "createLViewData" }, @@ -1340,6 +1337,9 @@ { "name": "createRootComponent" }, + { + "name": "createRootComponentView" + }, { "name": "createRootContext" }, @@ -1610,6 +1610,12 @@ { "name": "getComponentDef" }, + { + "name": "getComponentViewByIndex" + }, + { + "name": "getComponentViewByInstance" + }, { "name": "getContainerRenderParent" }, @@ -1673,9 +1679,6 @@ { "name": "getLContainer" }, - { - "name": "getLElementFromComponent" - }, { "name": "getLNode" }, @@ -1736,6 +1739,9 @@ { "name": "getNamedFormat" }, + { + "name": "getNative" + }, { "name": "getNgModuleDef" }, @@ -1793,9 +1799,6 @@ { "name": "getPreviousIndex" }, - { - "name": "getPreviousOrParentNode" - }, { "name": "getPreviousOrParentTNode" }, @@ -1865,9 +1868,6 @@ { "name": "hasValueChanged" }, - { - "name": "hostElement" - }, { "name": "hostReportError" }, @@ -2018,6 +2018,9 @@ { "name": "isScheduler" }, + { + "name": "isStylingContext" + }, { "name": "isTemplateElement" }, @@ -2111,9 +2114,6 @@ { "name": "nativeInsertBefore" }, - { - "name": "nativeNodeLocalRefExtractor" - }, { "name": "nextContext" }, diff --git a/packages/core/test/render3/change_detection_spec.ts b/packages/core/test/render3/change_detection_spec.ts index 368310e8c1..13889c08b0 100644 --- a/packages/core/test/render3/change_detection_spec.ts +++ b/packages/core/test/render3/change_detection_spec.ts @@ -6,17 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ +import {TemplateRef, ViewContainerRef} from '@angular/core'; import {withBody} from '@angular/private/testing'; import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck, RendererType2} from '../../src/core'; import {getRenderedText, whenRendered} from '../../src/render3/component'; import {directiveInject} from '../../src/render3/di'; -import {LifecycleHooksFeature, defineComponent, defineDirective} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, text, textBinding, tick} from '../../src/render3/instructions'; +import {LifecycleHooksFeature, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index'; +import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, reference, text, template, textBinding, tick} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer'; -import {containerEl, createComponent, renderComponent, requestAnimationFrame} from './render_util'; +import {ComponentFixture, containerEl, createComponent, renderComponent, requestAnimationFrame} from './render_util'; describe('change detection', () => { describe('markDirty, detectChanges, whenRendered, getRenderedText', () => { @@ -86,6 +87,73 @@ describe('change detection', () => { await whenRendered(myComp); expect(getRenderedText(myComp)).toEqual('updated'); })); + + it('should support detectChanges on components that have LContainers', () => { + let structuralComp !: StructuralComp; + + class StructuralComp { + tmp !: TemplateRef; + value = 'one'; + + constructor(public vcr: ViewContainerRef) {} + + create() { this.vcr.createEmbeddedView(this.tmp); } + + static ngComponentDef = defineComponent({ + type: StructuralComp, + selectors: [['structural-comp']], + factory: () => structuralComp = + new StructuralComp(directiveInject(ViewContainerRef as any)), + inputs: {tmp: 'tmp'}, + consts: 1, + vars: 1, + template: (rf: RenderFlags, ctx: StructuralComp) => { + if (rf & RenderFlags.Create) { + text(0); + } + if (rf & RenderFlags.Update) { + textBinding(0, bind(ctx.value)); + } + } + }); + } + + function FooTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + text(0, 'Temp content'); + } + } + + /** + * + * Temp content + * + * + */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + template(0, FooTemplate, 1, 0, '', null, ['foo', ''], templateRefExtractor); + element(2, 'structural-comp'); + } + if (rf & RenderFlags.Update) { + const foo = reference(1) as any; + elementProperty(2, 'tmp', bind(foo)); + } + }, 3, 1, [StructuralComp]); + + const fixture = new ComponentFixture(App); + fixture.update(); + expect(fixture.html).toEqual('one'); + + structuralComp.create(); + fixture.update(); + expect(fixture.html).toEqual('oneTemp content'); + + structuralComp.value = 'two'; + detectChanges(structuralComp); + expect(fixture.html).toEqual('twoTemp content'); + }); + }); describe('onPush', () => { diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index 817b9126c6..ac86d843b6 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -81,7 +81,7 @@ describe('instructions', () => { element(0, 'div', ['id', 'test', 'title', 'Hello']); }, () => {}, 1); - const div = (t.hostNode.native as HTMLElement).querySelector('div') !; + const div = (t.hostElement as HTMLElement).querySelector('div') !; expect(div.id).toEqual('test'); expect(div.title).toEqual('Hello'); expect(ngDevMode).toHaveProperties({ @@ -109,7 +109,7 @@ describe('instructions', () => { ]); }, () => {}, 1); - const div = (t.hostNode.native as HTMLElement).querySelector('div') !; + const div = (t.hostElement as HTMLElement).querySelector('div') !; const attrs: any = div.attributes; expect(attrs['id'].name).toEqual('id'); @@ -179,7 +179,7 @@ describe('instructions', () => { t.update(() => elementProperty(0, 'hidden', false)); // The hidden property would be true if `false` was stringified into `"false"`. - expect((t.hostNode.native as HTMLElement).querySelector('div') !.hidden).toEqual(false); + expect((t.hostElement as HTMLElement).querySelector('div') !.hidden).toEqual(false); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, tNode: 2, // 1 for div, 1 for host element diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index fff57738f6..75b5db9b5f 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -20,8 +20,9 @@ import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; import {NgIf} from './common_with_def'; import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util'; -import {MONKEY_PATCH_KEY_NAME, getContext} from '../../src/render3/context_discovery'; +import {getContext} from '../../src/render3/context_discovery'; import {StylingIndex} from '../../src/render3/interfaces/styling'; +import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; import {directiveInject} from '../../src/render3/di'; describe('render3 integration test', () => { @@ -1414,6 +1415,39 @@ describe('render3 integration test', () => { expect(fixture.html).toEqual(''); }); + it('should apply classes properly when nodes are components', () => { + const MyComp = createComponent('my-comp', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + text(0, 'Comp Content'); + } + }, 1, 0, []); + + /** + * + */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'my-comp'); + { elementStyling(['active']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementClassProp(0, 0, ctx.class); + elementStylingApply(0); + } + }, 1, 0, [MyComp]); + + const fixture = new ComponentFixture(App); + fixture.component.class = true; + fixture.update(); + expect(fixture.html).toEqual('Comp Content'); + + fixture.component.class = false; + fixture.update(); + expect(fixture.html).toEqual('Comp Content'); + }); + + it('should apply classes properly when nodes have LContainers', () => { let structuralComp !: StructuralComp; @@ -1448,7 +1482,7 @@ describe('render3 integration test', () => { /** * - * Content + * Temp Content * * */ @@ -1477,8 +1511,12 @@ describe('render3 integration test', () => { fixture.update(); expect(fixture.html) .toEqual('Comp ContentTemp Content'); - }); + fixture.component.class = false; + fixture.update(); + expect(fixture.html) + .toEqual('Comp ContentTemp Content'); + }); }); }); @@ -2168,12 +2206,11 @@ describe('render3 integration test', () => { const div1 = hostElm.querySelector('div:first-child') !as any; const div2 = hostElm.querySelector('div:last-child') !as any; const context = getContext(hostElm) !; - const elementNode = context.lViewData[context.nodeIndex]; - const elmData = elementNode.data !; + const componentView = context.lViewData[context.nodeIndex]; - expect(elmData).toContain(myDir1Instance); - expect(elmData).toContain(myDir2Instance); - expect(elmData).toContain(myDir3Instance); + expect(componentView).toContain(myDir1Instance); + expect(componentView).toContain(myDir2Instance); + expect(componentView).toContain(myDir3Instance); expect(Array.isArray((myDir1Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy(); expect(Array.isArray((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy(); @@ -2183,9 +2220,9 @@ describe('render3 integration test', () => { const d2Context = getContext(myDir2Instance) !; const d3Context = getContext(myDir3Instance) !; - expect(d1Context.lViewData).toEqual(elmData); - expect(d2Context.lViewData).toEqual(elmData); - expect(d3Context.lViewData).toEqual(elmData); + expect(d1Context.lViewData).toEqual(componentView); + expect(d2Context.lViewData).toEqual(componentView); + expect(d3Context.lViewData).toEqual(componentView); expect((myDir1Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(d1Context); expect((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME]).toBe(d2Context); @@ -2349,7 +2386,7 @@ describe('render3 integration test', () => { const context = getContext(child) !; expect(child[MONKEY_PATCH_KEY_NAME]).toBeTruthy(); - const componentData = context.lViewData[context.nodeIndex].data; + const componentData = context.lViewData[context.nodeIndex]; const component = componentData[CONTEXT]; expect(component instanceof ChildComp).toBeTruthy(); expect(component[MONKEY_PATCH_KEY_NAME]).toBe(context.lViewData); diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 152772b863..553a493c1a 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -25,7 +25,7 @@ import {DirectiveDefList, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeTyp import {LElementNode} from '../../src/render3/interfaces/node'; import {PlayerHandler} from '../../src/render3/interfaces/player'; import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; -import {HEADER_OFFSET} from '../../src/render3/interfaces/view'; +import {HEADER_OFFSET, LViewData} from '../../src/render3/interfaces/view'; import {Sanitizer} from '../../src/sanitization/security'; import {Type} from '../../src/type'; @@ -55,7 +55,7 @@ function noop() {} * - access to the render `html`. */ export class TemplateFixture extends BaseFixture { - hostNode: LElementNode; + hostView: LViewData; private _directiveDefs: DirectiveDefList|null; private _pipeDefs: PipeDefList|null; private _sanitizer: Sanitizer|null; @@ -78,7 +78,7 @@ export class TemplateFixture extends BaseFixture { this._pipeDefs = toDefs(pipes, extractPipeDef); this._sanitizer = sanitizer || null; this._rendererFactory = rendererFactory || domRendererFactory3; - this.hostNode = renderTemplate( + this.hostView = renderTemplate( this.hostElement, (rf: RenderFlags, ctx: any) => { if (rf & RenderFlags.Create) { @@ -99,8 +99,8 @@ export class TemplateFixture extends BaseFixture { */ update(updateBlock?: () => void): void { renderTemplate( - this.hostNode.native, updateBlock || this.updateBlock, 0, this.vars, null !, - this._rendererFactory, this.hostNode, this._directiveDefs, this._pipeDefs, this._sanitizer); + this.hostElement, updateBlock || this.updateBlock, 0, this.vars, null !, + this._rendererFactory, this.hostView, this._directiveDefs, this._pipeDefs, this._sanitizer); } } @@ -152,7 +152,7 @@ export class ComponentFixture extends BaseFixture { export const document = ((typeof global == 'object' && global || window) as any).document; export let containerEl: HTMLElement = null !; -let host: LElementNode|null; +let hostView: LViewData|null; const isRenderer2 = typeof process == 'object' && process.argv[3] && process.argv[3] === '--r=renderer2'; // tslint:disable-next-line:no-console @@ -181,7 +181,7 @@ export function resetDOM() { containerEl = document.createElement('div'); containerEl.setAttribute('host', ''); document.body.appendChild(containerEl); - host = null; + hostView = null; // TODO: assert that the global state is clean (e.g. ngData, previousOrParentNode, etc) } @@ -192,9 +192,9 @@ export function renderToHtml( template: ComponentTemplate, ctx: any, consts: number = 0, vars: number = 0, directives?: DirectiveTypesOrFactory | null, pipes?: PipeTypesOrFactory | null, providedRendererFactory?: RendererFactory3 | null) { - host = renderTemplate( + hostView = renderTemplate( containerEl, template, consts, vars, ctx, providedRendererFactory || testRendererFactory, - host, toDefs(directives, extractDirectiveDef), toDefs(pipes, extractPipeDef)); + hostView, toDefs(directives, extractDirectiveDef), toDefs(pipes, extractPipeDef)); return toHtml(containerEl); } diff --git a/packages/core/test/render3/styling/styling_spec.ts b/packages/core/test/render3/styling/styling_spec.ts index 662d1b24c3..b167f4ee40 100644 --- a/packages/core/test/render3/styling/styling_spec.ts +++ b/packages/core/test/render3/styling/styling_spec.ts @@ -10,10 +10,10 @@ import {InitialStylingFlags, RenderFlags} from '../../../src/render3/interfaces/ import {LElementNode} from '../../../src/render3/interfaces/node'; import {Renderer3} from '../../../src/render3/interfaces/renderer'; import {StylingContext, StylingFlags, StylingIndex} from '../../../src/render3/interfaces/styling'; -import {allocStylingContext, createStylingContextTemplate, isContextDirty, renderStyling as _renderStyling, setContextDirty, updateClassProp, updateStyleProp, updateStylingMap} from '../../../src/render3/styling/class_and_style_bindings'; +import {createStylingContextTemplate, isContextDirty, renderStyling as _renderStyling, setContextDirty, updateClassProp, updateStyleProp, updateStylingMap} from '../../../src/render3/styling/class_and_style_bindings'; +import {allocStylingContext} from '../../../src/render3/styling/util'; import {defaultStyleSanitizer} from '../../../src/sanitization/sanitization'; import {StyleSanitizeFn} from '../../../src/sanitization/style_sanitizer'; - import {renderToHtml} from '../render_util'; describe('styling', () => { @@ -110,19 +110,19 @@ describe('styling', () => { describe('createStylingContextTemplate', () => { it('should initialize empty template', () => { const template = initContext(); - expect(template).toEqual([element, null, null, [null], cleanStyle(0, 8), 0, null, null]); + expect(template).toEqual([null, null, [null], cleanStyle(0, 8), 0, element, null, null]); }); it('should initialize static styles', () => { const template = initContext([InitialStylingFlags.VALUES_MODE, 'color', 'red', 'width', '10px']); expect(template).toEqual([ - element, null, null, [null, 'red', '10px'], dirtyStyle(0, 14), // 0, + element, null, null, @@ -321,12 +321,12 @@ describe('styling', () => { updateStyles(stylingContext, {width: '100px', height: '100px'}); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 14), // 2, + element, null, {width: '100px', height: '100px'}, @@ -355,12 +355,12 @@ describe('styling', () => { updateStyles(stylingContext, {width: '200px', opacity: '0'}); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 14), // 2, + element, null, {width: '200px', opacity: '0'}, @@ -392,12 +392,12 @@ describe('styling', () => { getStyles(stylingContext); expect(stylingContext).toEqual([ - element, null, null, [null], cleanStyle(0, 14), // 2, + element, null, {width: '200px', opacity: '0'}, @@ -431,12 +431,12 @@ describe('styling', () => { updateStyleProp(stylingContext, 0, '300px'); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 14), // 2, + element, null, {width: null}, @@ -470,12 +470,12 @@ describe('styling', () => { updateStyleProp(stylingContext, 0, null); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 14), // 2, + element, null, {width: null}, @@ -514,12 +514,12 @@ describe('styling', () => { updateStyles(stylingContext, {width: '100px', height: '100px', opacity: '0.5'}); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 11), // 1, + element, null, {width: '100px', height: '100px', opacity: '0.5'}, @@ -553,12 +553,12 @@ describe('styling', () => { updateStyles(stylingContext, {}); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 11), // 1, + element, null, {}, @@ -594,12 +594,12 @@ describe('styling', () => { }); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 11), // 1, + element, null, {borderWidth: '5px'}, @@ -637,12 +637,12 @@ describe('styling', () => { updateStyleProp(stylingContext, 0, '200px'); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 11), // 1, + element, null, {borderWidth: '5px'}, @@ -680,12 +680,12 @@ describe('styling', () => { updateStyles(stylingContext, {borderWidth: '15px', borderColor: 'red'}); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 11), // 1, + element, null, {borderWidth: '15px', borderColor: 'red'}, @@ -737,12 +737,12 @@ describe('styling', () => { updateStyleProp(stylingContext, 0, '200px'); expect(stylingContext).toEqual([ - element, null, null, [null], dirtyStyle(0, 11), // 1, + element, null, {width: '100px'}, @@ -765,12 +765,12 @@ describe('styling', () => { getStyles(stylingContext); expect(stylingContext).toEqual([ - element, null, null, [null], cleanStyle(0, 11), // 1, + element, null, {width: '100px'}, @@ -802,12 +802,12 @@ describe('styling', () => { updateStyleProp(stylingContext, 1, '100px'); expect(stylingContext).toEqual([ - element, null, styleSanitizer, [null], dirtyStyle(0, 14), // 2, + element, null, null, @@ -835,12 +835,12 @@ describe('styling', () => { updateStyles(stylingContext, {'background-image': 'unsafe'}); expect(stylingContext).toEqual([ - element, null, styleSanitizer, [null], dirtyStyle(0, 14), // 2, + element, null, {'background-image': 'unsafe'}, @@ -873,12 +873,12 @@ describe('styling', () => { getStyles(stylingContext); expect(stylingContext).toEqual([ - element, null, styleSanitizer, [null], cleanStyle(0, 14), // 2, + element, null, {'background-image': 'unsafe'}, @@ -916,8 +916,8 @@ describe('styling', () => { const template = initContext(null, [InitialStylingFlags.VALUES_MODE, 'one', true, 'two', true]); expect(template).toEqual([ - element, null, null, [null, true, true], dirtyStyle(0, 14), // - 0, null, null, + null, null, [null, true, true], dirtyStyle(0, 14), // + 0, element, null, null, // #8 cleanClass(1, 14), 'one', null, @@ -979,12 +979,12 @@ describe('styling', () => { const initialClasses = ['wide', 'tall', InitialStylingFlags.VALUES_MODE, 'wide', true]; const stylingContext = initContext(initialStyles, initialClasses); expect(stylingContext).toEqual([ - element, null, null, [null, '100px', true], dirtyStyle(0, 20), // 2, + element, null, null, @@ -1033,12 +1033,12 @@ describe('styling', () => { updateStylingMap(stylingContext, 'tall round', {width: '200px', opacity: '0.5'}); expect(stylingContext).toEqual([ - element, null, null, [null, '100px', true], dirtyStyle(0, 20), // 2, + element, 'tall round', {width: '200px', opacity: '0.5'}, @@ -1101,12 +1101,12 @@ describe('styling', () => { updateStyleProp(stylingContext, 0, '300px'); expect(stylingContext).toEqual([ - element, null, null, [null, '100px', true], dirtyStyle(0, 20), // 2, + element, {tall: true, wide: true}, {width: '500px'}, @@ -1179,12 +1179,12 @@ describe('styling', () => { getStylesAndClasses(stylingContext); expect(stylingContext).toEqual([ - element, null, null, [null], cleanStyle(0, 8), // 0, + element, {foo: true}, {width: '200px'}, @@ -1208,12 +1208,12 @@ describe('styling', () => { getStylesAndClasses(stylingContext); expect(stylingContext).toEqual([ - element, null, null, [null], cleanStyle(0, 8), // 0, + element, {foo: false}, {width: '300px'}, @@ -1240,12 +1240,12 @@ describe('styling', () => { expect(getClasses(stylingContext)).toEqual({apple: true, orange: true, banana: true}); expect(stylingContext).toEqual([ - element, null, null, [null], cleanStyle(0, 8), // 0, + element, 'apple orange banana', null,